diff --git a/.pylintrc-local.yml b/.pylintrc-local.yml index b3478b18..c6882019 100644 --- a/.pylintrc-local.yml +++ b/.pylintrc-local.yml @@ -1,3 +1,6 @@ +- arg: py-version + val: '3.10' + - arg: ignore val: - firedrake diff --git a/examples/moving-geometry.py b/examples/moving-geometry.py index 81fe086d..ee1228b8 100644 --- a/examples/moving-geometry.py +++ b/examples/moving-geometry.py @@ -21,7 +21,6 @@ """ import logging -from typing import Optional, Type import numpy as np @@ -104,7 +103,7 @@ def advance(actx, dt, t, x, fn): def run(actx, *, ambient_dim: int = 3, - resolution: Optional[int] = None, + resolution: int | None = None, target_order: int = 4, tmax: float = 1.0, timestep: float = 1.0e-2, @@ -128,7 +127,7 @@ def run(actx, *, # a bit of work when reconstructing after a time step if group_factory_name == "warp_and_blend": - group_factory_cls: Type[poly.HomogeneousOrderBasedGroupFactory] = ( + group_factory_cls: type[poly.HomogeneousOrderBasedGroupFactory] = ( poly.PolynomialWarpAndBlend2DRestrictingGroupFactory) unit_nodes = mp.warp_and_blend_nodes(ambient_dim - 1, mesh_order) @@ -201,7 +200,8 @@ def source(t, x): gradx = sum( num_reference_derivative(discr, (i,), x) for i in range(discr.dim)) - intx = sum(actx.np.sum(xi * wi) for xi, wi in zip(x, discr.quad_weights())) + intx = sum(actx.np.sum(xi * wi) + for xi, wi in zip(x, discr.quad_weights(), strict=True)) assert gradx is not None assert intx is not None diff --git a/examples/simple-dg.py b/examples/simple-dg.py index 52ae4547..26722f1c 100644 --- a/examples/simple-dg.py +++ b/examples/simple-dg.py @@ -210,7 +210,8 @@ def grad(self, vec): for idim in range(self.volume_discr.dim)] return make_obj_array([ - sum(dref_i*ipder_i for dref_i, ipder_i in zip(dref, ipder[iambient])) + sum(dref_i*ipder_i + for dref_i, ipder_i in zip(dref, ipder[iambient], strict=True)) for iambient in range(self.volume_discr.ambient_dim)]) def div(self, vecs): @@ -259,7 +260,7 @@ def inverse_mass(self, vec): vec_i, arg_names=("mass_inv_mat", "vec"), tagged=(FirstAxisIsElementsTag(),) - ) for grp, vec_i in zip(discr.groups, vec) + ) for grp, vec_i in zip(discr.groups, vec, strict=True) ) ) / actx.thaw(self.vol_jacobian()) @@ -321,7 +322,8 @@ def face_mass(self, vec): ), tagged=(FirstAxisIsElementsTag(),)) for afgrp, volgrp, vec_i in zip(all_faces_discr.groups, - vol_discr.groups, vec) + vol_discr.groups, + vec, strict=True) ) ) diff --git a/meshmode/discretization/__init__.py b/meshmode/discretization/__init__.py index 1a0ab652..349cdb12 100644 --- a/meshmode/discretization/__init__.py +++ b/meshmode/discretization/__init__.py @@ -24,7 +24,8 @@ """ from abc import ABC, abstractmethod -from typing import Hashable, Iterable, Optional, Protocol, runtime_checkable +from collections.abc import Hashable, Iterable +from typing import Protocol, runtime_checkable from warnings import warn import numpy as np @@ -353,7 +354,7 @@ def __init__(self, actx: ArrayContext, mesh: _Mesh, group_factory: ElementGroupFactory, - real_dtype: Optional[np.dtype] = None, + real_dtype: np.dtype | None = None, _force_actx_clone: bool = True) -> None: """ :arg actx: an :class:`arraycontext.ArrayContext` used to perform @@ -397,10 +398,10 @@ def __init__(self, self._cached_nodes = None def copy(self, - actx: Optional[ArrayContext] = None, - mesh: Optional[_Mesh] = None, - group_factory: Optional[ElementGroupFactory] = None, - real_dtype: Optional[np.dtype] = None) -> "Discretization": + actx: ArrayContext | None = None, + mesh: _Mesh | None = None, + group_factory: ElementGroupFactory | None = None, + real_dtype: np.dtype | None = None) -> "Discretization": """Creates a new object of the same type with all arguments that are not *None* replaced. The copy is not recursive. """ @@ -461,7 +462,7 @@ def _new_array(self, actx, creation_func, dtype=None): for grp in self.groups))) def empty(self, actx: ArrayContext, - dtype: Optional[np.dtype] = None) -> _DOFArray: + dtype: np.dtype | None = None) -> _DOFArray: """Return an empty :class:`~meshmode.dof_array.DOFArray`. :arg dtype: type special value 'c' will result in a @@ -479,7 +480,7 @@ def empty(self, actx: ArrayContext, return self._new_array(actx, actx.np.zeros, dtype=dtype) def zeros(self, actx: ArrayContext, - dtype: Optional[np.dtype] = None) -> _DOFArray: + dtype: np.dtype | None = None) -> _DOFArray: """Return a zero-initialized :class:`~meshmode.dof_array.DOFArray`. :arg dtype: type special value 'c' will result in a diff --git a/meshmode/discretization/connection/__init__.py b/meshmode/discretization/connection/__init__.py index fe3ac157..e0bed877 100644 --- a/meshmode/discretization/connection/__init__.py +++ b/meshmode/discretization/connection/__init__.py @@ -137,7 +137,7 @@ def check_connection(actx: ArrayContext, connection: DirectDiscretizationConnect assert len(connection.groups) == len(to_discr.groups) - for cgrp, tgrp in zip(connection.groups, to_discr.groups): + for cgrp, tgrp in zip(connection.groups, to_discr.groups, strict=True): for batch in cgrp.batches: fgrp = from_discr.groups[batch.from_group_index] diff --git a/meshmode/discretization/connection/chained.py b/meshmode/discretization/connection/chained.py index 9bc7e581..ba313c64 100644 --- a/meshmode/discretization/connection/chained.py +++ b/meshmode/discretization/connection/chained.py @@ -151,7 +151,7 @@ def _build_batches(actx, from_bins, to_bins, batch): def to_device(x): return actx.freeze(actx.from_numpy(np.asarray(x))) - for ibatch, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins)): + for ibatch, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins, strict=True)): yield InterpolationBatch( from_group_index=batch[ibatch].from_group_index, from_element_indices=to_device(from_bin), @@ -248,7 +248,7 @@ def flatten_chained_connection(actx, connection): # build new groups groups = [] - for igrp, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins)): + for igrp, (from_bin, to_bin) in enumerate(zip(from_bins, to_bins, strict=True)): groups.append(DiscretizationConnectionElementGroup( list(_build_batches(actx, from_bin, to_bin, batch_info[igrp])))) diff --git a/meshmode/discretization/connection/direct.py b/meshmode/discretization/connection/direct.py index 209744a5..a0763ccd 100644 --- a/meshmode/discretization/connection/direct.py +++ b/meshmode/discretization/connection/direct.py @@ -21,8 +21,9 @@ """ from abc import ABC, abstractmethod +from collections.abc import Sequence from dataclasses import dataclass -from typing import Generic, List, Optional, Sequence, Tuple +from typing import Generic import numpy as np @@ -51,7 +52,7 @@ def _reshape_and_preserve_tags( - actx: ArrayContext, ary: ArrayT, new_shape: Tuple[int, ...]) -> ArrayT: + actx: ArrayContext, ary: ArrayT, new_shape: tuple[int, ...]) -> ArrayT: try: tags = ary.tags except AttributeError: @@ -126,11 +127,11 @@ class InterpolationBatch(Generic[ArrayT]): from_element_indices: ArrayT to_element_indices: ArrayT result_unit_nodes: np.ndarray - to_element_face: Optional[int] + to_element_face: int | None def __post_init__(self): - self._global_from_element_indices_cache: \ - Optional[Tuple[ArrayT, ArrayT]] = None + self._global_from_element_indices_cache: ( + tuple[ArrayT, ArrayT] | None) = None @property def nelements(self) -> int: @@ -138,7 +139,7 @@ def nelements(self) -> int: def _global_from_element_indices( self, actx: ArrayContext, to_group: ElementGroupBase - ) -> Tuple[ArrayT, ArrayT]: + ) -> tuple[ArrayT, ArrayT]: """Returns a version of :attr:`from_element_indices` that is usable without :attr:`to_element_indices`, consisting of a tuple. The first entry of the tuple is an array of flags indicating @@ -408,7 +409,7 @@ def _resample_matrix(self, actx: ArrayContext, to_group_index: int, # {{{ _resample_point_pick_indices def _resample_point_pick_indices(self, to_group_index: int, ibatch_index: int, - tol_multiplier: Optional[float] = None): + tol_multiplier: float | None = None): """If :meth:`_resample_matrix` *R* is a row subset of a permutation matrix *P*, return the index subset I so that ``x[I] == R @ x`` up to machine epsilon multiplied by *tol_multiplier* (or an internally @@ -431,7 +432,7 @@ def _resample_point_pick_indices(self, to_group_index: int, ibatch_index: int, tol_multiplier=None: (to_group_index, ibatch_index, tol_multiplier)) def _frozen_resample_point_pick_indices(self, actx: ArrayContext, to_group_index: int, ibatch_index: int, - tol_multiplier: Optional[float] = None): + tol_multiplier: float | None = None): result = self._resample_point_pick_indices( to_group_index=to_group_index, ibatch_index=ibatch_index, @@ -444,7 +445,7 @@ def _frozen_resample_point_pick_indices(self, actx: ArrayContext, # }}} @memoize_method - def is_permutation(self, tol_multiplier: Optional[float] = None) -> bool: + def is_permutation(self, tol_multiplier: float | None = None) -> bool: """Return *True* if no interpolation is used in applying this connection, i.e. if the source unit nodes in the connection (cf. :class:`InterpolationBatch.result_unit_nodes`) match up @@ -463,7 +464,7 @@ def is_permutation(self, tol_multiplier: Optional[float] = None) -> bool: def _per_target_group_pick_info( self, actx: ArrayContext, i_tgrp: int - ) -> Optional[Sequence[_FromGroupPickData]]: + ) -> Sequence[_FromGroupPickData] | None: """Returns a list of :class:`_FromGroupPickData`, one per source group from which data is to be transferred, or *None*, if conditions for this representation are not met. @@ -487,7 +488,7 @@ def _per_target_group_pick_info( if not batch_source_groups: return None - result: List[_FromGroupPickData] = [] + result: list[_FromGroupPickData] = [] for source_group_index in batch_source_groups: batch_indices_for_this_source_group = [ i for i, batch in enumerate(cgrp.batches) @@ -552,7 +553,7 @@ def _per_target_group_pick_info( def _global_point_pick_info( self, actx: ArrayContext - ) -> Sequence[Optional[Sequence[_FromGroupPickData]]]: + ) -> Sequence[Sequence[_FromGroupPickData] | None]: if self._global_point_pick_info_cache is not None: return self._global_point_pick_info_cache @@ -708,7 +709,7 @@ def group_pick_knl(is_surjective: bool): group_arrays = [] for i_tgrp, (cgrp, group_pick_info) in enumerate( - zip(self.groups, self._global_point_pick_info(actx))): + zip(self.groups, self._global_point_pick_info(actx), strict=True)): group_array_contributions = [] @@ -925,7 +926,7 @@ def knl(): tgt_node_nr_base = 0 mats = [] for i_tgrp, (tgrp, cgrp) in enumerate( - zip(conn.to_discr.groups, conn.groups)): + zip(conn.to_discr.groups, conn.groups, strict=True)): for i_batch, batch in enumerate(cgrp.batches): if not len(batch.from_element_indices): continue diff --git a/meshmode/discretization/connection/face.py b/meshmode/discretization/connection/face.py index e56cba40..3130a3d9 100644 --- a/meshmode/discretization/connection/face.py +++ b/meshmode/discretization/connection/face.py @@ -22,7 +22,6 @@ import logging from dataclasses import dataclass -from typing import Optional import numpy as np @@ -165,7 +164,7 @@ def make_face_restriction( discr: Discretization, group_factory: ElementGroupFactory, boundary_tag: BoundaryTag, - per_face_groups: Optional[bool] = False + per_face_groups: bool | None = False ) -> DirectDiscretizationConnection: """Create a mesh, a discretization and a connection to restrict a function on *discr* to its values on the edges of element faces @@ -232,7 +231,7 @@ def make_face_restriction( connection_data = {} for igrp, (grp, fagrp_list) in enumerate( - zip(discr.groups, discr.mesh.facial_adjacency_groups)): + zip(discr.groups, discr.mesh.facial_adjacency_groups, strict=True)): mgrp = grp.mesh_el_group @@ -252,7 +251,7 @@ def make_face_restriction( if isinstance(fagrp, InteriorAdjacencyGroup)] for fagrp in int_grps: group_boundary_faces.extend( - zip(fagrp.elements, fagrp.element_faces)) + zip(fagrp.elements, fagrp.element_faces, strict=True)) elif boundary_tag is FACE_RESTR_ALL: group_boundary_faces.extend( @@ -271,7 +270,8 @@ def make_face_restriction( group_boundary_faces.extend( zip( bdry_grp.elements, - bdry_grp.element_faces)) + bdry_grp.element_faces, + strict=True)) # }}} @@ -383,7 +383,7 @@ def make_face_to_all_faces_embedding( actx: ArrayContext, faces_connection: DirectDiscretizationConnection, all_faces_discr: Discretization, - from_discr: Optional[Discretization] = None + from_discr: Discretization | None = None ) -> DirectDiscretizationConnection: """Return a :class:`meshmode.discretization.connection.DiscretizationConnection` diff --git a/meshmode/discretization/connection/projection.py b/meshmode/discretization/connection/projection.py index bbc66538..7e87c1b7 100644 --- a/meshmode/discretization/connection/projection.py +++ b/meshmode/discretization/connection/projection.py @@ -258,7 +258,7 @@ def vandermonde_matrix(grp): c_i, arg_names=("vdm", "coeffs"), tagged=(FirstAxisIsElementsTag(),)) - for grp, c_i in zip(self.to_discr.groups, coefficients) + for grp, c_i in zip(self.to_discr.groups, coefficients, strict=True) ) ) diff --git a/meshmode/discretization/connection/refinement.py b/meshmode/discretization/connection/refinement.py index f3cf63ce..f8ded63c 100644 --- a/meshmode/discretization/connection/refinement.py +++ b/meshmode/discretization/connection/refinement.py @@ -84,7 +84,8 @@ def _build_interpolation_batches_for_group( assert len(refinement_result) == num_children # Refined -> interpolates to children for from_bin, to_bin, child_idx in zip( - from_bins[1:], to_bins[1:], refinement_result): + from_bins[1:], to_bins[1:], refinement_result, + strict=True): from_bin.append(elt_idx) to_bin.append(child_idx) @@ -97,8 +98,10 @@ def _build_interpolation_batches_for_group( from itertools import chain for from_bin, to_bin, unit_nodes in zip( - from_bins, to_bins, - chain([fine_unit_nodes], mapped_unit_nodes)): + from_bins, + to_bins, + chain([fine_unit_nodes], mapped_unit_nodes), + strict=True): if not from_bin: continue yield InterpolationBatch( @@ -148,8 +151,10 @@ def make_refinement_connection(actx, refiner, coarse_discr, group_factory): groups = [] for group_idx, (coarse_discr_group, fine_discr_group, record) in \ - enumerate(zip(coarse_discr.groups, fine_discr.groups, - refiner.group_refinement_records)): + enumerate(zip(coarse_discr.groups, + fine_discr.groups, + refiner.group_refinement_records, + strict=True)): groups.append( DiscretizationConnectionElementGroup( list(_build_interpolation_batches_for_group( diff --git a/meshmode/discretization/connection/same_mesh.py b/meshmode/discretization/connection/same_mesh.py index 2b88aa4e..7583ed22 100644 --- a/meshmode/discretization/connection/same_mesh.py +++ b/meshmode/discretization/connection/same_mesh.py @@ -43,7 +43,8 @@ def make_same_mesh_connection(actx, to_discr, from_discr): return IdentityDiscretizationConnection(from_discr) groups = [] - for igrp, (fgrp, tgrp) in enumerate(zip(from_discr.groups, to_discr.groups)): + for igrp, (fgrp, tgrp) in enumerate( + zip(from_discr.groups, to_discr.groups, strict=True)): from arraycontext.metadata import NameHint all_elements = actx.tag(NameHint(f"all_el_ind_grp{igrp}"), actx.tag_axis(0, diff --git a/meshmode/discretization/poly_element.py b/meshmode/discretization/poly_element.py index 91adcf4d..056da022 100644 --- a/meshmode/discretization/poly_element.py +++ b/meshmode/discretization/poly_element.py @@ -535,7 +535,8 @@ def quadrature_rule(self): else: nodes_tp = self._nodes - for idim, (nodes, basis) in enumerate(zip(nodes_tp, self._basis.bases)): + for idim, (nodes, basis) in enumerate( + zip(nodes_tp, self._basis.bases, strict=True)): # get current dimension's nodes iaxis = (*(0,)*idim, slice(None), *(0,)*(self.dim-idim-1)) nodes = nodes[iaxis] @@ -939,9 +940,8 @@ def default_simplex_group_factory(base_dim, order): """ try: - # recursivenodes is only importable in Python 3.8 since - # it uses :func:`math.comb`, so need to check if it can - # be imported. + # FIXME: this is a hard dependency (in pyproject.toml) now, so this + # shouldn't be needed import recursivenodes # noqa: F401 except ImportError: # If it cannot be imported, use warp-and-blend nodes. diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py index da56c679..b4305931 100644 --- a/meshmode/discretization/visualization.py +++ b/meshmode/discretization/visualization.py @@ -26,7 +26,7 @@ import logging from dataclasses import dataclass from functools import singledispatch -from typing import Any, Dict, List, Optional, Tuple +from typing import Any import numpy as np @@ -218,7 +218,7 @@ def _check_discr_same_connectivity(discr, other): if not all( sg.discretization_key() == og.discretization_key() and sg.nelements == og.nelements - for sg, og in zip(discr.groups, other.groups)): + for sg, og in zip(discr.groups, other.groups, strict=True)): return False return True @@ -482,7 +482,8 @@ def cells(self): grp.nunit_dofs, grp.nelements * grp.nunit_dofs + 1, grp.nunit_dofs) - for grp_offset, grp in zip(grp_offsets, self.vis_discr.groups) + for grp_offset, grp in zip(grp_offsets[:-1], self.vis_discr.groups, + strict=True) ]) return self.vis_discr.mesh.nelements, connectivity, offsets @@ -864,13 +865,13 @@ def write_vtk_file(self, file_name, names_and_fields, # {{{ vtkhdf def write_vtkhdf_file(self, - file_name: str, names_and_fields: List[Tuple[str, Any]], *, + file_name: str, names_and_fields: list[tuple[str, Any]], *, comm=None, use_high_order: bool = False, real_only: bool = False, overwrite: bool = False, - h5_file_options: Optional[Dict[str, Any]] = None, - dset_options: Optional[Dict[str, Any]] = None) -> None: + h5_file_options: dict[str, Any] | None = None, + dset_options: dict[str, Any] | None = None) -> None: """Write a VTK HDF5 file (typical extension ``'.hdf'``) containing the visualization fields in *names_and_fields*. @@ -1161,7 +1162,8 @@ def write_xdmf_file(self, file_name, names_and_fields, grids = [] node_nr_base = 0 - for igrp, (vgrp, gnodes) in enumerate(zip(connectivity.groups, nodes)): + for igrp, (vgrp, gnodes) in enumerate( + zip(connectivity.groups, nodes, strict=True)): grp_name = f"Group_{igrp:05d}" h5grp = h5grid.create_group(grp_name) @@ -1318,7 +1320,7 @@ def make_visualizer(actx, discr, vis_order=None, vis_discr = discr.copy(actx=actx, group_factory=VisGroupFactory(vis_order)) if all(grp.discretization_key() == vgrp.discretization_key() - for grp, vgrp in zip(discr.groups, vis_discr.groups)): + for grp, vgrp in zip(discr.groups, vis_discr.groups, strict=True)): from warnings import warn warn("Visualization discretization is identical to base discretization. " "To avoid the creation of a separate discretization for " @@ -1383,7 +1385,7 @@ def write_nodal_adjacency_vtk_file(file_name, mesh, (mesh.ambient_dim, mesh.nelements), dtype=mesh.vertices.dtype) - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): centroids[:, base_element_nr:base_element_nr + grp.nelements] = ( np.sum(mesh.vertices[:, grp.vertex_indices], axis=-1) / grp.vertex_indices.shape[-1]) diff --git a/meshmode/distributed.py b/meshmode/distributed.py index dea86521..6de36eaf 100644 --- a/meshmode/distributed.py +++ b/meshmode/distributed.py @@ -35,8 +35,9 @@ THE SOFTWARE. """ +from collections.abc import Hashable, Mapping, Sequence from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, Hashable, List, Mapping, Sequence, Union, cast +from typing import TYPE_CHECKING, Any, cast from warnings import warn import numpy as np @@ -158,7 +159,7 @@ def receive_mesh_part(self): # between two parts on the same rank. @dataclass class RemoteGroupInfo: - inter_part_adj_groups: List[InterPartAdjacencyGroup] + inter_part_adj_groups: list[InterPartAdjacencyGroup] vol_elem_indices: np.ndarray bdry_elem_indices: np.ndarray bdry_faces: np.ndarray @@ -237,12 +238,12 @@ class MPIBoundaryCommSetupHelper: def __init__(self, mpi_comm: "mpi4py.MPI.Intracomm", actx: ArrayContext, - inter_rank_bdry_info: Union[ + inter_rank_bdry_info: ( # new-timey - Sequence[InterRankBoundaryInfo], + Sequence[InterRankBoundaryInfo] # old-timey, for compatibility - Mapping[int, DirectDiscretizationConnection], - ], + | Mapping[int, DirectDiscretizationConnection] + ), bdry_grp_factory: ElementGroupFactory): """ :arg bdry_grp_factory: Group factory to use when creating the remote-to-local @@ -357,7 +358,7 @@ def complete_some(self): raise ValueError( "duplicate local/remote part pair in inter_rank_bdry_info") - for i_src_rank, recvd in zip(source_ranks, data): + for i_src_rank, recvd in zip(source_ranks, data, strict=True): (remote_part_id, local_part_id, remote_bdry_mesh, remote_group_infos) = recvd diff --git a/meshmode/dof_array.py b/meshmode/dof_array.py index 205c0b5c..b422b3e5 100644 --- a/meshmode/dof_array.py +++ b/meshmode/dof_array.py @@ -25,10 +25,11 @@ import operator as op import threading +from collections.abc import Callable, Iterable from contextlib import contextmanager from functools import partial, update_wrapper from numbers import Number -from typing import Any, Callable, Iterable +from typing import Any from warnings import warn import numpy as np @@ -591,7 +592,7 @@ def check_dofarray_against_discr(discr, dof_ary: DOFArray): "DOFArray has unexpected number of groups " f"({len(dof_ary)}, expected: {len(discr.groups)})") - for i, (grp, grp_ary) in enumerate(zip(discr.groups, dof_ary)): + for i, (grp, grp_ary) in enumerate(zip(discr.groups, dof_ary, strict=True)): expected_shape = (grp.nelements, grp.nunit_dofs) if grp_ary.shape != expected_shape: raise InconsistentDOFArray( diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index f1830b04..b857db08 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -303,7 +303,8 @@ def _get_firedrake_facial_adjacency_groups(fdrake_mesh_topology, to_keep = np.isin(int_elements, cells_to_use) cells_to_use_inv = dict(zip(cells_to_use, np.arange(np.size(cells_to_use), - dtype=IntType))) + dtype=IntType), + strict=True)) # Keep the cells that we are using and change old cell index # to new cell index @@ -459,7 +460,8 @@ def _get_firedrake_orientations(fdrake_mesh, unflipped_group, vertices, orient = np.ones(num_cells) if normals: for i, (normal, vert_indices) in enumerate( - zip(np.array(normals), unflipped_group.vertex_indices)): + zip(np.array(normals), unflipped_group.vertex_indices, + strict=True)): edge = vertices[:, vert_indices[1]] - vertices[:, vert_indices[0]] if np.cross(normal, edge) < 0: orient[i] = -1.0 @@ -612,8 +614,8 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, # Get all the nodal information we can from the topology with ProcessLogger(logger, "Retrieving vertex indices and computing " "NodalAdjacency from firedrake mesh"): - vertex_indices, nodal_adjacency = \ - _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use) + vertex_indices, nodal_adjacency = ( + _get_firedrake_nodal_info(fdrake_mesh, cells_to_use=cells_to_use)) # If only using some cells, vertices may need new indices as many # will be removed @@ -621,9 +623,10 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, vert_ndx_new2old = np.unique(vertex_indices.flatten()) vert_ndx_old2new = dict(zip(vert_ndx_new2old, np.arange(np.size(vert_ndx_new2old), - dtype=vertex_indices.dtype))) - vertex_indices = \ - np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices) + dtype=vertex_indices.dtype), + strict=True)) + vertex_indices = ( + np.vectorize(vert_ndx_old2new.__getitem__)(vertex_indices)) with ProcessLogger(logger, "Building (possibly) unflipped " "SimplexElementGroup from firedrake unit nodes/nodes"): @@ -872,7 +875,8 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): group = mesh.groups[group_nr] fd2mm_indices = np.unique(group.vertex_indices.flatten()) coords = mesh.vertices[:, fd2mm_indices].T - mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)))) + mm2fd_indices = dict(zip(fd2mm_indices, np.arange(np.size(fd2mm_indices)), + strict=True)) cells = np.vectorize(mm2fd_indices.__getitem__)(group.vertex_indices) # Get a dmplex object and then a mesh topology diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index c1137489..d4b90cb6 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -26,25 +26,19 @@ """ from abc import ABC, abstractmethod +from collections.abc import Callable, Collection, Hashable, Iterable, Mapping, Sequence from dataclasses import InitVar, dataclass, field, replace from typing import ( Any, - Callable, ClassVar, - Collection, - Hashable, - Iterable, Literal, - Mapping, - Sequence, + TypeAlias, TypeVar, - Union, ) from warnings import warn import numpy as np import numpy.linalg as la -from typing_extensions import TypeAlias import modepy as mp from pytools import memoize_method @@ -758,13 +752,13 @@ def as_python(self) -> str: # {{{ mesh -DTypeLike = Union[np.dtype, np.generic] -NodalAdjacencyLike = Union[ - Literal[False], Iterable[np.ndarray], NodalAdjacency -] -FacialAdjacencyLike = Union[ - Literal[False], Sequence[Sequence[FacialAdjacencyGroup]] -] +DTypeLike = np.dtype | np.generic +NodalAdjacencyLike = ( + Literal[False] | Iterable[np.ndarray] | NodalAdjacency + ) +FacialAdjacencyLike = ( + Literal[False] | Sequence[Sequence[FacialAdjacencyGroup]] + ) def check_mesh_consistency( @@ -1562,7 +1556,7 @@ def _compute_nodal_adjacency_from_vertices(mesh: Mesh) -> NodalAdjacency: _, nvertices = mesh.vertices.shape vertex_to_element: list[list[int]] = [[] for i in range(nvertices)] - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): if grp.vertex_indices is None: raise ValueError("unable to compute nodal adjacency without vertices") @@ -1571,7 +1565,7 @@ def _compute_nodal_adjacency_from_vertices(mesh: Mesh) -> NodalAdjacency: vertex_to_element[ivertex].append(base_element_nr + iel_grp) element_to_element: list[set[int]] = [set() for i in range(mesh.nelements)] - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): assert grp.vertex_indices is not None for iel_grp in range(grp.nelements): diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py index 323fa66e..597c7cdc 100644 --- a/meshmode/mesh/generation.py +++ b/meshmode/mesh/generation.py @@ -23,7 +23,8 @@ """ import logging -from typing import Any, Callable, Dict, Optional, Sequence, Tuple, Type, Union +from collections.abc import Callable, Sequence +from typing import Any import numpy as np import numpy.linalg as la @@ -370,8 +371,8 @@ def random(seed: int) -> "NArmedStarfish": def make_curve_mesh( curve_f: Callable[[np.ndarray], np.ndarray], element_boundaries: np.ndarray, order: int, *, - unit_nodes: Optional[np.ndarray] = None, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, + unit_nodes: np.ndarray | None = None, + node_vertex_consistency_tolerance: float | bool | None = None, closed: bool = True, return_parametrization_points: bool = False) -> Mesh: """ @@ -454,8 +455,8 @@ def make_curve_mesh( @deprecate_keyword("group_factory", "group_cls") def make_group_from_vertices( vertices: np.ndarray, vertex_indices: np.ndarray, order: int, *, - group_cls: Optional[type] = None, - unit_nodes: Optional[np.ndarray] = None) -> MeshElementGroup: + group_cls: type[MeshElementGroup] | None = None, + unit_nodes: np.ndarray | None = None) -> MeshElementGroup: # shape: (ambient_dim, nelements, nvertices) ambient_dim = vertices.shape[0] el_vertices = vertices[:, vertex_indices] @@ -544,8 +545,8 @@ def make_group_from_vertices( def generate_icosahedron( r: float, order: int, *, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None) -> Mesh: # https://en.wikipedia.org/w/index.php?title=Icosahedron&oldid=387737307 phi = (1+5**(1/2))/2 @@ -585,8 +586,8 @@ def generate_icosahedron( def generate_cube_surface(r: float, order: int, *, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None) -> Mesh: shape = mp.Hypercube(3) vertices = mp.unit_vertices_for_shape(shape) vertices *= r / la.norm(vertices, ord=2, axis=0) @@ -612,8 +613,8 @@ def generate_cube_surface(r: float, order: int, *, def generate_icosphere(r: float, order: int, *, uniform_refinement_rounds: int = 0, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None) -> Mesh: from warnings import warn warn("'generate_icosphere' is deprecated and will be removed in 2023. " "Use 'generate_sphere' instead.", @@ -629,9 +630,9 @@ def generate_icosphere(r: float, order: int, *, def generate_sphere(r: float, order: int, *, uniform_refinement_rounds: int = 0, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None, - group_cls: Optional[type] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None, + group_cls: type[MeshElementGroup] | None = None) -> Mesh: """ :arg r: radius of the sphere. :arg order: order of the group elements. If *unit_nodes* is also @@ -691,8 +692,8 @@ def generate_surface_of_revolution( height_discr: np.ndarray, angle_discr: np.ndarray, order: int, *, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None) -> Mesh: """Return a cylinder aligned with the "height" axis aligned with the Z axis. :arg get_radius: A callable function that takes in a 1D array of heights @@ -761,9 +762,9 @@ def ensure_radius(arr: np.ndarray) -> np.ndarray: def generate_torus_and_cycle_vertices( r_major: float, r_minor: float, n_major: int = 20, n_minor: int = 10, order: int = 1, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None, - group_cls: Optional[type] = None, + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None, + group_cls: type[MeshElementGroup] | None = None, ) -> Mesh: a = r_major b = r_minor @@ -868,9 +869,9 @@ def idx(i: int, j: int) -> int: def generate_torus( r_major: float, r_minor: float, n_major: int = 20, n_minor: int = 10, order: int = 1, - node_vertex_consistency_tolerance: Optional[Union[float, bool]] = None, - unit_nodes: Optional[np.ndarray] = None, - group_cls: Optional[type] = None) -> Mesh: + node_vertex_consistency_tolerance: float | bool | None = None, + unit_nodes: np.ndarray | None = None, + group_cls: type[MeshElementGroup] | None = None) -> Mesh: r"""Generate a torus. .. tikz:: A torus with major circle (magenta) and minor circle (red). @@ -948,7 +949,7 @@ def refine_mesh_and_get_urchin_warper( order: int, m: int, n: int, est_rel_interp_tolerance: float, min_rad: float = 0.2, uniform_refinement_rounds: int = 0 - ) -> Tuple[Refiner, Callable[[Mesh], Mesh]]: + ) -> tuple[Refiner, Callable[[Mesh], Mesh]]: """ :arg order: order of the (simplex) elements. :arg m: order of the spherical harmonic :math:`Y^m_n`. @@ -1062,14 +1063,14 @@ def generate_urchin( @deprecate_keyword("group_factory", "group_cls") def generate_box_mesh( - axis_coords: Tuple[np.ndarray, ...], + axis_coords: tuple[np.ndarray, ...], order: int = 1, *, coord_dtype: Any = np.float64, - periodic: Optional[bool] = None, - group_cls: Optional[Type[MeshElementGroup]] = None, - boundary_tag_to_face: Optional[Dict[Any, str]] = None, - mesh_type: Optional[str] = None, - unit_nodes: Optional[np.ndarray] = None) -> Mesh: + periodic: bool | None = None, + group_cls: type[MeshElementGroup] | None = None, + boundary_tag_to_face: dict[Any, str] | None = None, + mesh_type: str | None = None, + unit_nodes: np.ndarray | None = None) -> Mesh: r"""Create a semi-structured mesh. :arg axis_coords: a tuple with a number of entries corresponding @@ -1418,14 +1419,14 @@ def generate_box_mesh( def generate_regular_rect_mesh( a: Sequence[float] = (0, 0), b: Sequence[float] = (1, 1), *, - nelements_per_axis: Optional[int] = None, - npoints_per_axis: Optional[int] = None, - periodic: Optional[bool] = None, + nelements_per_axis: int | None = None, + npoints_per_axis: int | None = None, + periodic: bool | None = None, order: int = 1, - boundary_tag_to_face: Optional[Dict[Any, str]] = None, - group_cls: Optional[Type[MeshElementGroup]] = None, - mesh_type: Optional[str] = None, - n: Optional[int] = None, + boundary_tag_to_face: dict[Any, str] | None = None, + group_cls: type[MeshElementGroup] | None = None, + mesh_type: str | None = None, + n: int | None = None, ) -> Mesh: """Create a semi-structured rectangular mesh with equispaced elements. @@ -1478,7 +1479,7 @@ def generate_regular_rect_mesh( "lower topological dimension and map it.)") axis_coords = [np.linspace(a_i, b_i, npoints_i) - for a_i, b_i, npoints_i in zip(a, b, npoints_per_axis)] + for a_i, b_i, npoints_i in zip(a, b, npoints_per_axis, strict=False)] return generate_box_mesh(axis_coords, order=order, periodic=periodic, @@ -1493,10 +1494,10 @@ def generate_regular_rect_mesh( def generate_warped_rect_mesh( dim: int, order: int, *, - nelements_side: Optional[int] = None, - npoints_side: Optional[int] = None, - group_cls: Optional[Type[MeshElementGroup]] = None, - n: Optional[int] = None) -> Mesh: + nelements_side: int | None = None, + npoints_side: int | None = None, + group_cls: type[MeshElementGroup] | None = None, + n: int | None = None) -> Mesh: """Generate a mesh of a warped square/cube. Mainly useful for testing functionality with curvilinear meshes. """ @@ -1571,7 +1572,7 @@ def generate_annular_cylinder_slice_mesh( "+z": ["+z"], }) - def transform(x: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + def transform(x: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]: r = inner_radius*(1 - x[0]) + outer_radius*x[0] theta = -np.pi/4*(1 - x[1]) + np.pi/4*x[1] z = -0.5*(1 - x[2]) + 0.5*x[2] @@ -1606,7 +1607,7 @@ def transform(x: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: @log_process(logger) def warp_and_refine_until_resolved( - unwarped_mesh_or_refiner: Union[Mesh, Refiner], + unwarped_mesh_or_refiner: Mesh | Refiner, warp_callable: Callable[[Mesh], Mesh], est_rel_interp_tolerance: float) -> Mesh: """Given an original ("unwarped") :class:`meshmode.mesh.Mesh` and a @@ -1654,7 +1655,8 @@ def warp_and_refine_until_resolved( "(NaN or Inf)") for base_element_nr, egrp in zip( - warped_mesh.base_element_nrs, warped_mesh.groups): + warped_mesh.base_element_nrs, warped_mesh.groups, + strict=True): if not isinstance(egrp, SimplexElementGroup): raise TypeError( f"Unsupported element group type: '{type(egrp).__name__}'") diff --git a/meshmode/mesh/io.py b/meshmode/mesh/io.py index f3d3a326..71dacd09 100644 --- a/meshmode/mesh/io.py +++ b/meshmode/mesh/io.py @@ -185,8 +185,10 @@ def get_mesh(self, return_tag_to_elements_map=False): i = 0 for el_vertices, el_nodes, el_type, el_markers in zip( - self.element_vertices, self.element_nodes, self.element_types, - self.element_markers): + self.element_vertices, + self.element_nodes, + self.element_types, + self.element_markers, strict=True): if el_type is not group_el_type: continue diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index 2a96744a..5020364f 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -22,19 +22,10 @@ THE SOFTWARE. """ +from collections.abc import Callable, Mapping, Sequence from dataclasses import dataclass, replace from functools import reduce -from typing import ( - Callable, - Dict, - List, - Literal, - Mapping, - Optional, - Sequence, - Tuple, - Union, -) +from typing import Literal import numpy as np import numpy.linalg as la @@ -80,7 +71,7 @@ def find_group_indices( - groups: List[MeshElementGroup], + groups: Sequence[MeshElementGroup], meshwide_elems: np.ndarray) -> np.ndarray: """ :arg groups: A list of :class:`~meshmode.mesh.MeshElementGroup` instances @@ -134,7 +125,7 @@ def _compute_global_elem_to_part_elem( def _filter_mesh_groups( mesh: Mesh, selected_elements: np.ndarray, - vertex_id_dtype: np.dtype) -> Tuple[List[MeshElementGroup], np.ndarray]: + vertex_id_dtype: np.dtype) -> tuple[list[MeshElementGroup], np.ndarray]: """ Create new mesh groups containing a selected subset of elements. @@ -254,8 +245,8 @@ def _create_self_to_self_adjacency_groups( mesh: Mesh, global_elem_to_part_elem: np.ndarray, self_part_index: int, - self_mesh_groups: List[MeshElementGroup], - self_mesh_group_elem_base: List[int]) -> List[List[InteriorAdjacencyGroup]]: + self_mesh_groups: Sequence[MeshElementGroup], + self_mesh_group_elem_base: Sequence[int]) -> list[list[InteriorAdjacencyGroup]]: r""" Create self-to-self facial adjacency groups for a partitioned mesh. @@ -274,7 +265,7 @@ def _create_self_to_self_adjacency_groups( corresponding to the entries in *mesh.facial_adjacency_groups* that have self-to-self adjacency. """ - self_to_self_adjacency_groups: List[List[InteriorAdjacencyGroup]] = [ + self_to_self_adjacency_groups: list[list[InteriorAdjacencyGroup]] = [ [] for _ in self_mesh_groups] for igrp, facial_adj_list in enumerate(mesh.facial_adjacency_groups): @@ -324,9 +315,9 @@ def _create_self_to_other_adjacency_groups( part_id_to_part_index: Mapping[PartID, int], global_elem_to_part_elem: np.ndarray, self_part_id: PartID, - self_mesh_groups: List[MeshElementGroup], - self_mesh_group_elem_base: List[int], - connected_parts: Sequence[PartID]) -> List[List[InterPartAdjacencyGroup]]: + self_mesh_groups: Sequence[MeshElementGroup], + self_mesh_group_elem_base: Sequence[int], + connected_parts: Sequence[PartID]) -> list[list[InterPartAdjacencyGroup]]: """ Create self-to-other adjacency groups for the partitioned mesh. @@ -350,7 +341,7 @@ def _create_self_to_other_adjacency_groups( """ self_part_index = part_id_to_part_index[self_part_id] - self_to_other_adj_groups: List[List[InterPartAdjacencyGroup]] = [ + self_to_other_adj_groups: list[list[InterPartAdjacencyGroup]] = [ [] for _ in self_mesh_groups] for igrp, facial_adj_list in enumerate(mesh.facial_adjacency_groups): @@ -406,8 +397,8 @@ def _create_boundary_groups( mesh: Mesh, global_elem_to_part_elem: np.ndarray, self_part_index: PartID, - self_mesh_groups: List[MeshElementGroup], - self_mesh_group_elem_base: List[int]) -> List[List[BoundaryAdjacencyGroup]]: + self_mesh_groups: Sequence[MeshElementGroup], + self_mesh_group_elem_base: Sequence[int]) -> list[list[BoundaryAdjacencyGroup]]: """ Create boundary groups for partitioned mesh. @@ -426,7 +417,7 @@ def _create_boundary_groups( corresponding to the entries in *mesh.facial_adjacency_groups* that have boundary faces. """ - bdry_adj_groups: List[List[BoundaryAdjacencyGroup]] = [ + bdry_adj_groups: list[list[BoundaryAdjacencyGroup]] = [ [] for _ in self_mesh_groups] for igrp, facial_adj_list in enumerate(mesh.facial_adjacency_groups): @@ -526,7 +517,7 @@ def _get_mesh_part( mesh, global_elem_to_part_elem, self_part_index, self_mesh_groups, self_mesh_group_elem_base) - def _gather_grps(igrp: int) -> List[FacialAdjacencyGroup]: + def _gather_grps(igrp: int) -> list[FacialAdjacencyGroup]: self_grps: Sequence[FacialAdjacencyGroup] = self_to_self_adj_groups[igrp] other_grps: Sequence[FacialAdjacencyGroup] = self_to_other_adj_groups[igrp] bdry_grps: Sequence[FacialAdjacencyGroup] = boundary_adj_groups[igrp] @@ -547,7 +538,7 @@ def _gather_grps(igrp: int) -> List[FacialAdjacencyGroup]: def partition_mesh( mesh: Mesh, part_id_to_elements: Mapping[PartID, np.ndarray], - return_parts: Optional[Sequence[PartID]] = None) -> Mapping[PartID, Mesh]: + return_parts: Sequence[PartID] | None = None) -> Mapping[PartID, Mesh]: """ :arg mesh: A :class:`~meshmode.mesh.Mesh` to be partitioned. :arg part_id_to_elements: A :class:`dict` mapping a part identifier to @@ -597,7 +588,7 @@ def evec(i: int) -> np.ndarray: result[i] = 1 return result - def unpack_single(ary: Optional[np.ndarray]) -> np.ndarray: + def unpack_single(ary: np.ndarray | None) -> np.ndarray: assert ary is not None item, = ary return item @@ -660,7 +651,7 @@ def find_volume_mesh_element_orientations( result: np.ndarray = np.empty(mesh.nelements, dtype=np.float64) - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): result_grp_view = result[base_element_nr:base_element_nr + grp.nelements] try: @@ -686,8 +677,8 @@ def find_volume_mesh_element_orientations( def get_simplex_element_flip_matrix( order: int, unit_nodes: np.ndarray, - permutation: Optional[Tuple[int, ...]] = None, - ) -> Tuple[np.ndarray, np.ndarray]: + permutation: tuple[int, ...] | None = None, + ) -> tuple[np.ndarray, np.ndarray]: """ Generate a resampling matrix that corresponds to a permutation of the barycentric coordinates being applied. @@ -746,7 +737,7 @@ def get_simplex_element_flip_matrix( def _get_tensor_product_element_flip_matrix_and_vertex_permutation( grp: TensorProductElementGroup, - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray]: unit_flip_matrix = np.eye(grp.dim) unit_flip_matrix[0, 0] = -1 @@ -833,7 +824,7 @@ def perform_flips( flip_flags = flip_flags.astype(bool) new_groups = [] - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): grp_flip_flags = flip_flags[base_element_nr:base_element_nr + grp.nelements] if grp_flip_flags.any(): @@ -853,7 +844,7 @@ def perform_flips( # {{{ bounding box -def find_bounding_box(mesh: Mesh) -> Tuple[np.ndarray, np.ndarray]: +def find_bounding_box(mesh: Mesh) -> tuple[np.ndarray, np.ndarray]: """ :return: a tuple *(min, max)*, each consisting of a :class:`numpy.ndarray` indicating the minimal and maximal extent of the geometry along each axis. @@ -908,11 +899,11 @@ def merge_disjoint_meshes( # {{{ assemble new groups list - nodal_adjacency: Optional[Literal[False]] = None + nodal_adjacency: Literal[False] | None = None if any(mesh._nodal_adjacency is not None for mesh in meshes): nodal_adjacency = False - facial_adjacency_groups: Optional[Literal[False]] = None + facial_adjacency_groups: Literal[False] | None = None if any(mesh._facial_adjacency_groups is not None for mesh in meshes): facial_adjacency_groups = False @@ -928,7 +919,7 @@ def merge_disjoint_meshes( group_vertex_indices = [] group_nodes = [] - for mesh, vert_base in zip(meshes, vert_bases): + for mesh, vert_base in zip(meshes, vert_bases, strict=True): for group in mesh.groups: assert group.vertex_indices is not None group_vertex_indices.append(group.vertex_indices + vert_base) @@ -945,7 +936,7 @@ def merge_disjoint_meshes( else: new_groups = [] - for mesh, vert_base in zip(meshes, vert_bases): + for mesh, vert_base in zip(meshes, vert_bases, strict=True): for group in mesh.groups: assert group.vertex_indices is not None new_vertex_indices = group.vertex_indices + vert_base @@ -971,7 +962,7 @@ def split_mesh_groups( mesh: Mesh, element_flags: np.ndarray, return_subgroup_mapping: bool = False, - ) -> Union[Mesh, Tuple[Mesh, Dict[Tuple[int, int], int]]]: + ) -> Mesh | tuple[Mesh, dict[tuple[int, int], int]]: """Split all the groups in *mesh* according to the values of *element_flags*. The element flags are expected to be integers defining, for each group, how the elements are to be split into @@ -995,11 +986,11 @@ def split_mesh_groups( """ assert element_flags.shape == (mesh.nelements,) - new_groups: List[MeshElementGroup] = [] + new_groups: list[MeshElementGroup] = [] subgroup_to_group_map = {} for igrp, (base_element_nr, grp) in enumerate( - zip(mesh.base_element_nrs, mesh.groups) + zip(mesh.base_element_nrs, mesh.groups, strict=True) ): assert grp.vertex_indices is not None grp_flags = element_flags[base_element_nr:base_element_nr + grp.nelements] @@ -1034,9 +1025,9 @@ def _match_vertices( mesh: Mesh, src_vertex_indices: np.ndarray, tgt_vertex_indices: np.ndarray, *, - aff_map: Optional[AffineMap] = None, + aff_map: AffineMap | None = None, tol: float = 1e-12, - use_tree: Optional[bool] = None) -> np.ndarray: + use_tree: bool | None = None) -> np.ndarray: if mesh.vertices is None: raise ValueError("Mesh must have vertices") @@ -1178,7 +1169,7 @@ def _get_face_vertex_indices(mesh: Mesh, face_ids: _FaceIDs) -> np.ndarray: def _match_boundary_faces( mesh: Mesh, bdry_pair_mapping: BoundaryPairMapping, tol: float, *, - use_tree: Optional[bool] = None) -> Tuple[_FaceIDs, _FaceIDs]: + use_tree: bool | None = None) -> tuple[_FaceIDs, _FaceIDs]: """ Given a :class:`BoundaryPairMapping` *bdry_pair_mapping*, return the correspondence between faces of the two boundaries (expressed as a pair of @@ -1279,8 +1270,8 @@ def _match_boundary_faces( def glue_mesh_boundaries( mesh: Mesh, - bdry_pair_mappings_and_tols: List[Tuple[BoundaryPairMapping, float]], *, - use_tree: Optional[bool] = None) -> Mesh: + bdry_pair_mappings_and_tols: Sequence[tuple[BoundaryPairMapping, float]], *, + use_tree: bool | None = None) -> Mesh: """ Create a new mesh from *mesh* in which one or more pairs of boundaries are "glued" together such that the boundary surfaces become part of the interior @@ -1437,8 +1428,8 @@ def map_mesh(mesh: Mesh, f: Callable[[np.ndarray], np.ndarray]) -> Mesh: def affine_map( mesh: Mesh, - A: Optional[Union[np.generic, np.ndarray]] = None, - b: Optional[Union[np.generic, np.ndarray]] = None) -> Mesh: + A: np.generic | np.ndarray | None = None, + b: np.generic | np.ndarray | None = None) -> Mesh: """Apply the affine map :math:`f(x) = A x + b` to the geometry of *mesh*.""" if A is not None and not isinstance(A, np.ndarray): @@ -1507,7 +1498,7 @@ def compute_new_map(old_map: AffineMap) -> AffineMap: fagrp_list = [] for old_fagrp in old_fagrp_list: if isinstance(old_fagrp, - (InteriorAdjacencyGroup, InterPartAdjacencyGroup)): + InteriorAdjacencyGroup | InterPartAdjacencyGroup): new_fagrp: FacialAdjacencyGroup = replace( old_fagrp, aff_map=compute_new_map(old_fagrp.aff_map)) else: @@ -1557,7 +1548,7 @@ def _get_rotation_matrix_from_angle_and_axis( def rotate_mesh_around_axis( mesh: Mesh, *, theta: float, - axis: Optional[np.ndarray] = None) -> Mesh: + axis: np.ndarray | None = None) -> Mesh: """Rotate the mesh by *theta* radians around the axis *axis*. :arg axis: a (not necessarily unit) vector. By default, the rotation is @@ -1585,8 +1576,8 @@ def rotate_mesh_around_axis( def make_mesh_grid( mesh: Mesh, *, - shape: Tuple[int, ...], - offset: Optional[Tuple[np.ndarray, ...]] = None, + shape: tuple[int, ...], + offset: tuple[np.ndarray, ...] | None = None, skip_tests: bool = False) -> Mesh: """Constructs a grid of copies of *mesh*, with *shape* copies in each dimensions at the given *offset*. @@ -1615,7 +1606,7 @@ def make_mesh_grid( meshes = [] for index in product(*(range(n) for n in shape)): - b = sum((i * o for i, o in zip(index, offset)), offset[0]) + b = sum((i * o for i, o in zip(index, offset, strict=True)), offset[0]) meshes.append(affine_map(mesh, b=b)) return merge_disjoint_meshes(meshes, skip_tests=skip_tests) @@ -1629,7 +1620,7 @@ def remove_unused_vertices(mesh: Mesh) -> Mesh: if mesh.vertices is None: raise ValueError("mesh must have vertices") - def not_none(vi: Optional[np.ndarray]) -> np.ndarray: + def not_none(vi: np.ndarray | None) -> np.ndarray: if vi is None: raise ValueError("mesh element groups must have vertex indices") return vi diff --git a/meshmode/mesh/refinement/no_adjacency.py b/meshmode/mesh/refinement/no_adjacency.py index 66be8744..bc171113 100644 --- a/meshmode/mesh/refinement/no_adjacency.py +++ b/meshmode/mesh/refinement/no_adjacency.py @@ -93,7 +93,8 @@ def refine(self, refine_flags): get_group_tessellation_info, ) - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, + strict=True): el_tess_info = get_group_tessellation_info(grp) # {{{ compute counts and index arrays @@ -153,7 +154,8 @@ def refine(self, refine_flags): for imidpoint, (iref_midpoint, (v1, v2)) in enumerate(zip( el_tess_info.midpoint_indices, - el_tess_info.midpoint_vertex_pairs)): + el_tess_info.midpoint_vertex_pairs, + strict=True)): global_v1 = grp.vertex_indices[old_iel, v1] global_v2 = grp.vertex_indices[old_iel, v2] diff --git a/meshmode/mesh/refinement/tessellate.py b/meshmode/mesh/refinement/tessellate.py index 2f4b3a20..d4798976 100644 --- a/meshmode/mesh/refinement/tessellate.py +++ b/meshmode/mesh/refinement/tessellate.py @@ -27,7 +27,6 @@ import logging from dataclasses import dataclass from functools import singledispatch -from typing import List, Optional, Tuple import numpy as np @@ -73,11 +72,11 @@ class ElementTessellationInfo: """ children: np.ndarray - ref_vertices: List[Tuple[int, ...]] + ref_vertices: list[tuple[int, ...]] - orig_vertex_indices: Optional[np.ndarray] = None - midpoint_indices: Optional[np.ndarray] = None - midpoint_vertex_pairs: Optional[List[Tuple[int, int]]] = None + orig_vertex_indices: np.ndarray | None = None + midpoint_indices: np.ndarray | None = None + midpoint_vertex_pairs: list[tuple[int, int]] | None = None @dataclass(frozen=True) @@ -95,7 +94,7 @@ class GroupRefinementRecord: el_tess_info: ElementTessellationInfo # FIXME: This should really be a CSR data structure. - element_mapping: List[List[int]] + element_mapping: list[list[int]] @singledispatch @@ -151,7 +150,7 @@ def midpoint(x, y): return d - return tuple(midpoint(ai, bi) for ai, bi in zip(a, b)) + return tuple(midpoint(ai, bi) for ai, bi in zip(a, b, strict=True)) def _get_ref_midpoints(shape, ref_vertices): @@ -202,7 +201,7 @@ def _get_group_midpoints_modepy( resampled_midpoints = np.einsum("mu,deu->edm", resampling_mat, meg.nodes[:, elements]) - return dict(zip(elements, resampled_midpoints)) + return dict(zip(elements, resampled_midpoints, strict=True)) @get_group_tessellated_nodes.register(_ModepyElementGroup) diff --git a/meshmode/mesh/refinement/utils.py b/meshmode/mesh/refinement/utils.py index ce90d9a5..8dcb3ce8 100644 --- a/meshmode/mesh/refinement/utils.py +++ b/meshmode/mesh/refinement/utils.py @@ -25,7 +25,6 @@ import logging from abc import ABC, abstractmethod from functools import singledispatch -from typing import Optional import numpy as np @@ -60,7 +59,7 @@ def __init__(self, mesh: Mesh) -> None: def get_current_mesh(self) -> Mesh: return self._current_mesh - def get_previous_mesh(self) -> Optional[Mesh]: + def get_previous_mesh(self) -> Mesh | None: return self._previous_mesh def refine_uniformly(self): diff --git a/meshmode/mesh/tools.py b/meshmode/mesh/tools.py index a69bf9f8..9a59873d 100644 --- a/meshmode/mesh/tools.py +++ b/meshmode/mesh/tools.py @@ -20,8 +20,6 @@ THE SOFTWARE. """ -from typing import Optional - import numpy as np import numpy.linalg as la @@ -68,7 +66,7 @@ def make_element_lookup_tree(mesh, eps=1e-12): # }}} -def optional_array_equal(a: Optional[np.ndarray], b: Optional[np.ndarray]) -> bool: +def optional_array_equal(a: np.ndarray | None, b: np.ndarray | None) -> bool: if a is None: return b is None else: @@ -222,8 +220,8 @@ def __ne__(self, other): def find_point_permutation( targets: np.ndarray, permutees: np.ndarray, - tol_multiplier: Optional[float] = None - ) -> Optional[np.ndarray]: + tol_multiplier: float | None = None + ) -> np.ndarray | None: """ :arg targets: shaped ``(dim, npoints)`` or just ``(dim,)`` if a single point :arg permutees: shaped ``(dim, npoints)`` diff --git a/meshmode/mesh/visualization.py b/meshmode/mesh/visualization.py index 42ee7fdc..4f25a83e 100644 --- a/meshmode/mesh/visualization.py +++ b/meshmode/mesh/visualization.py @@ -20,7 +20,7 @@ THE SOFTWARE. """ -from typing import Any, Dict, Optional +from typing import Any import numpy as np @@ -45,7 +45,7 @@ def draw_2d_mesh( mesh: Mesh, *, - rng: Optional[np.random.Generator] = None, + rng: np.random.Generator | None = None, draw_vertex_numbers: bool = True, draw_element_numbers: bool = True, draw_nodal_adjacency: bool = False, @@ -86,7 +86,7 @@ def draw_2d_mesh( pathdata.append( (Path.CLOSEPOLY, (elverts[0, 0], elverts[1, 0]))) - codes, verts = zip(*pathdata) + codes, verts = zip(*pathdata, strict=True) path = Path(verts, codes) patch = mpatches.PathPatch(path, **kwargs) pt.gca().add_patch(patch) @@ -175,9 +175,9 @@ def global_iel_to_group_and_iel(global_iel): def draw_curve( mesh: Mesh, *, el_bdry_style: str = "o", - el_bdry_kwargs: Optional[Dict[str, Any]] = None, + el_bdry_kwargs: dict[str, Any] | None = None, node_style: str = "x-", - node_kwargs: Optional[Dict[str, Any]] = None) -> None: + node_kwargs: dict[str, Any] | None = None) -> None: """Draw a curve mesh. :arg el_bdry_kwargs: passed to ``plot`` when drawing elements. @@ -212,7 +212,7 @@ def draw_curve( def write_vertex_vtk_file( mesh: Mesh, file_name: str, *, - compressor: Optional[str] = None, + compressor: str | None = None, overwrite: bool = False) -> None: # {{{ create cell_types from pyvisfile.vtk import ( @@ -231,7 +231,7 @@ def write_vertex_vtk_file( cell_types = np.empty(mesh.nelements, dtype=np.uint8) cell_types.fill(255) - for base_element_nr, egrp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, egrp in zip(mesh.base_element_nrs, mesh.groups, strict=True): if isinstance(egrp, SimplexElementGroup): vtk_cell_type = { 1: VTK_LINE, @@ -314,7 +314,7 @@ def mesh_to_tikz(mesh: Mesh) -> str: drawel_lines = [] drawel_lines.append(r"\def\drawelements#1{") - for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups): + for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): for iel, el in enumerate(grp.vertex_indices): el_nr = base_element_nr + iel + 1 elverts = mesh.vertices[:, el] diff --git a/meshmode/version.py b/meshmode/version.py index 6784d82a..6e2608de 100644 --- a/meshmode/version.py +++ b/meshmode/version.py @@ -1,8 +1,7 @@ from importlib import metadata -from typing import Tuple -def _parse_version(version: str) -> Tuple[Tuple[int, ...], str]: +def _parse_version(version: str) -> tuple[tuple[int, ...], str]: import re m = re.match("^([0-9.]+)([a-z0-9]*?)$", VERSION_TEXT) diff --git a/pyproject.toml b/pyproject.toml index c8446702..f3173785 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,14 +6,14 @@ requires = [ [project] name = "meshmode" -version = "2021.2" +version = "2024.0" description = "High-order polynomial discretizations of and on meshes" readme = "README.rst" license = { text = "MIT" } authors = [ { name = "Andreas Kloeckner", email = "inform@tiker.net" }, ] -requires-python = ">=3.8" +requires-python = ">=3.10" classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", @@ -39,9 +39,6 @@ dependencies = [ "pymbolic>=2022.2", "pytools>=2022.1", "recursivenodes", - - # for TypeAlias - "typing-extensions>=4; python_version<'3.10'", ] [project.optional-dependencies] @@ -144,7 +141,7 @@ markers = [ ] [tool.mypy] -python_version = "3.8" +python_version = "3.10" warn_unused_ignores = true [[tool.mypy.overrides]] diff --git a/run-pylint.sh b/run-pylint.sh index 83ab854d..0d9a5cdc 100755 --- a/run-pylint.sh +++ b/run-pylint.sh @@ -20,4 +20,4 @@ if [[ -f .pylintrc-local.yml ]]; then PYLINT_RUNNER_ARGS+=" --yaml-rcfile=.pylintrc-local.yml" fi -python .run-pylint.py $PYLINT_RUNNER_ARGS $(basename $PWD) examples/*.py test/test_*.py experiments/*.py "$@" +python .run-pylint.py $PYLINT_RUNNER_ARGS $(basename $PWD) examples/*.py test/test_*.py "$@" diff --git a/test/test_firedrake_interop.py b/test/test_firedrake_interop.py index 6aaaec02..bd5ba3fe 100644 --- a/test/test_firedrake_interop.py +++ b/test/test_firedrake_interop.py @@ -358,7 +358,7 @@ def test_bdy_tags(mesh_name, bdy_ids, coord_indices, coord_values, square_or_cube_mesh.topology.topology_dm, square_or_cube_mesh.exterior_facets.facets), return_counts=True) assert set(fdrake_bdy_ids) == set(bdy_ids) - for bdy_id, fdrake_count in zip(fdrake_bdy_ids, fdrake_counts): + for bdy_id, fdrake_count in zip(fdrake_bdy_ids, fdrake_counts, strict=True): assert fdrake_count == bdy_id_to_mm_count[bdy_id] # Now make sure we have identified the correct faces @@ -369,7 +369,7 @@ def test_bdy_tags(mesh_name, bdy_ids, coord_indices, coord_values, if grp.boundary_tag == bdy_id] assert len(matching_ext_grps) == 1 ext_grp = matching_ext_grps[0] - for iel, ifac in zip(ext_grp.elements, ext_grp.element_faces): + for iel, ifac in zip(ext_grp.elements, ext_grp.element_faces, strict=True): el_vert_indices = mm_mesh.groups[0].vertex_indices[iel] # numpy nb: have to have comma to use advanced indexing face_vert_indices = el_vert_indices[face_vertex_indices[ifac], ] @@ -672,7 +672,7 @@ def test_from_fd_idempotency(actx_factory, atol=CLOSE_ATOL) else: for dof_arr_cp, dof_arr in zip(mm_field_copy.flatten(), - mm_field.flatten()): + mm_field.flatten(), strict=True): np.testing.assert_allclose(actx.to_numpy(dof_arr_cp[0]), actx.to_numpy(dof_arr[0]), atol=CLOSE_ATOL) diff --git a/test/test_mesh.py b/test/test_mesh.py index c471e095..d792308f 100644 --- a/test/test_mesh.py +++ b/test/test_mesh.py @@ -1462,7 +1462,7 @@ def separated(x, y): assert all( separated(mgrid.groups[i].nodes, mgrid.groups[j].nodes) - for i, j in zip(range(m), range(m)) if i != j) + for i, j in zip(range(m), range(m), strict=True) if i != j) if not visualize: return diff --git a/test/test_partition.py b/test/test_partition.py index e179aef3..e57fac57 100644 --- a/test/test_partition.py +++ b/test/test_partition.py @@ -267,7 +267,7 @@ def test_partition_mesh(mesh_size, num_parts, num_groups, dim, scramble_parts): if isinstance(fagrp, InterPartAdjacencyGroup)] for ipagrp in ipagrps: for i, (elem, face) in enumerate( - zip(ipagrp.elements, ipagrp.element_faces)): + zip(ipagrp.elements, ipagrp.element_faces, strict=True)): index_lookup_table[ipart, igrp, elem, face] = i ipagrp_count = 0