diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index a63dccc3..269d96fe 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -9757,6 +9757,62 @@ "lineCount": 1 } }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, { "code": "reportReturnType", "range": { @@ -9805,10 +9861,98 @@ "lineCount": 1 } }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, { "code": "reportUnknownMemberType", "range": { "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 39, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 44, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 44, "endColumn": 48, "lineCount": 1 } @@ -9817,10 +9961,50 @@ "code": "reportUnknownMemberType", "range": { "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 46, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 51, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 51, "endColumn": 55, "lineCount": 1 } }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, { "code": "reportUnknownMemberType", "range": { @@ -9830,10 +10014,26 @@ } }, { - "code": "reportReturnType", + "code": "reportAttributeAccessIssue", "range": { - "startColumn": 29, - "endColumn": 34, + "startColumn": 46, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 51, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 51, + "endColumn": 55, "lineCount": 1 } }, @@ -11325,14 +11525,6 @@ "lineCount": 1 } }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 40, - "lineCount": 1 - } - }, { "code": "reportUnknownVariableType", "range": { @@ -12253,14 +12445,6 @@ "lineCount": 1 } }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 34, - "endColumn": 79, - "lineCount": 1 - } - }, { "code": "reportUnknownArgumentType", "range": { @@ -14424,8 +14608,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 48, - "endColumn": 53, + "startColumn": 40, + "endColumn": 45, "lineCount": 1 } }, @@ -14440,24 +14624,24 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 47, - "endColumn": 60, + "startColumn": 40, + "endColumn": 53, "lineCount": 1 } }, { "code": "reportUnknownArgumentType", "range": { - "startColumn": 47, - "endColumn": 65, + "startColumn": 40, + "endColumn": 58, "lineCount": 1 } }, { "code": "reportUnknownArgumentType", "range": { - "startColumn": 63, - "endColumn": 71, + "startColumn": 57, + "endColumn": 65, "lineCount": 1 } }, @@ -14512,16 +14696,16 @@ { "code": "reportUnknownMemberType", "range": { - "startColumn": 47, - "endColumn": 75, + "startColumn": 32, + "endColumn": 60, "lineCount": 1 } }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 47, - "endColumn": 81, + "startColumn": 32, + "endColumn": 66, "lineCount": 1 } }, @@ -14904,8 +15088,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 35, - "endColumn": 40, + "startColumn": 74, + "endColumn": 79, "lineCount": 1 } }, @@ -15336,17 +15520,9 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 33, - "endColumn": 71, - "lineCount": 2 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 35, - "endColumn": 45, - "lineCount": 1 + "startColumn": 35, + "endColumn": 45, + "lineCount": 1 } }, { @@ -15376,8 +15552,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 63, - "endColumn": 68, + "startColumn": 57, + "endColumn": 62, "lineCount": 1 } }, @@ -15552,8 +15728,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 65, - "endColumn": 69, + "startColumn": 59, + "endColumn": 63, "lineCount": 1 } }, @@ -16392,8 +16568,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 31, - "endColumn": 44, + "startColumn": 51, + "endColumn": 64, "lineCount": 1 } }, @@ -16421,19 +16597,11 @@ "lineCount": 1 } }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 25, - "endColumn": 43, - "lineCount": 4 - } - }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 27, - "endColumn": 41, + "startColumn": 59, + "endColumn": 73, "lineCount": 1 } }, @@ -16488,8 +16656,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 43, - "endColumn": 54, + "startColumn": 60, + "endColumn": 71, "lineCount": 1 } }, @@ -16512,24 +16680,16 @@ { "code": "reportUnknownMemberType", "range": { - "startColumn": 35, - "endColumn": 58, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 35, - "endColumn": 58, + "startColumn": 52, + "endColumn": 75, "lineCount": 1 } }, { "code": "reportAttributeAccessIssue", "range": { - "startColumn": 47, - "endColumn": 58, + "startColumn": 64, + "endColumn": 75, "lineCount": 1 } }, @@ -16832,16 +16992,16 @@ { "code": "reportUnknownMemberType", "range": { - "startColumn": 50, - "endColumn": 62, + "startColumn": 37, + "endColumn": 49, "lineCount": 1 } }, { "code": "reportUnknownArgumentType", "range": { - "startColumn": 50, - "endColumn": 62, + "startColumn": 37, + "endColumn": 49, "lineCount": 1 } }, @@ -19010,8 +19170,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 43, - "endColumn": 54, + "startColumn": 37, + "endColumn": 48, "lineCount": 1 } }, @@ -20018,8 +20178,8 @@ { "code": "reportUnknownArgumentType", "range": { - "startColumn": 44, - "endColumn": 48, + "startColumn": 38, + "endColumn": 42, "lineCount": 1 } }, @@ -22165,62 +22325,6 @@ } ], "./meshmode/mesh/__init__.py": [ - { - "code": "reportUnannotatedClassAttribute", - "range": { - "startColumn": 13, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 16, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 15, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 17, - "lineCount": 1 - } - }, { "code": "reportUnknownMemberType", "range": { @@ -22237,94 +22341,6 @@ "lineCount": 1 } }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 18, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 17, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 17, - "lineCount": 1 - } - }, { "code": "reportCallInDefaultInitializer", "range": { @@ -22373,14 +22389,6 @@ "lineCount": 1 } }, - { - "code": "reportImplicitOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, { "code": "reportUnknownMemberType", "range": { @@ -22397,35 +22405,11 @@ "lineCount": 1 } }, - { - "code": "reportUnknownVariableType", - "range": { - "startColumn": 8, - "endColumn": 11, - "lineCount": 1 - } - }, - { - "code": "reportUnknownVariableType", - "range": { - "startColumn": 4, - "endColumn": 20, - "lineCount": 1 - } - }, { "code": "reportUnknownArgumentType", "range": { - "startColumn": 35, - "endColumn": 80, - "lineCount": 1 - } - }, - { - "code": "reportUnusedVariable", - "range": { - "startColumn": 49, - "endColumn": 50, + "startColumn": 20, + "endColumn": 71, "lineCount": 1 } }, @@ -22437,14 +22421,6 @@ "lineCount": 1 } }, - { - "code": "reportUnusedVariable", - "range": { - "startColumn": 52, - "endColumn": 53, - "lineCount": 1 - } - }, { "code": "reportUnknownMemberType", "range": { @@ -22453,14 +22429,6 @@ "lineCount": 1 } }, - { - "code": "reportRedeclaration", - "range": { - "startColumn": 12, - "endColumn": 33, - "lineCount": 1 - } - }, { "code": "reportUnknownMemberType", "range": { @@ -22477,62 +22445,6 @@ "lineCount": 1 } }, - { - "code": "reportUnknownVariableType", - "range": { - "startColumn": 8, - "endColumn": 21, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 60, - "endColumn": 73, - "lineCount": 1 - } - }, - { - "code": "reportUnknownMemberType", - "range": { - "startColumn": 8, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 37, - "endColumn": 55, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 8, - "endColumn": 11, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 21, - "endColumn": 34, - "lineCount": 1 - } - }, { "code": "reportUnknownVariableType", "range": { @@ -22542,27 +22454,19 @@ } }, { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 44, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportUnknownMemberType", + "code": "reportAssignmentType", "range": { - "startColumn": 8, - "endColumn": 38, - "lineCount": 1 + "startColumn": 21, + "endColumn": 29, + "lineCount": 3 } }, { - "code": "reportUnknownArgumentType", + "code": "reportAssignmentType", "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 + "startColumn": 21, + "endColumn": 29, + "lineCount": 3 } }, { @@ -22605,59 +22509,19 @@ "lineCount": 1 } }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 11, - "endColumn": 67, - "lineCount": 1 - } - }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 42, - "endColumn": 67, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 11, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportUnknownMemberType", - "range": { - "startColumn": 43, - "endColumn": 69, + "startColumn": 37, + "endColumn": 62, "lineCount": 1 } }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 8, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 42, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 44, - "endColumn": 48, + "startColumn": 38, + "endColumn": 64, "lineCount": 1 } } @@ -23563,6 +23427,14 @@ } ], "./meshmode/mesh/processing.py": [ + { + "code": "reportInvalidCast", + "range": { + "startColumn": 8, + "endColumn": 64, + "lineCount": 2 + } + }, { "code": "reportUnknownMemberType", "range": { @@ -23571,6 +23443,14 @@ "lineCount": 2 } }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 16, + "endColumn": 40, + "lineCount": 3 + } + }, { "code": "reportUnknownMemberType", "range": { @@ -23747,6 +23627,14 @@ "lineCount": 1 } }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 11, + "endColumn": 36, + "lineCount": 1 + } + }, { "code": "reportUnknownMemberType", "range": { @@ -24063,14 +23951,6 @@ "lineCount": 1 } }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 29, - "endColumn": 68, - "lineCount": 1 - } - }, { "code": "reportUnknownVariableType", "range": { @@ -25515,14 +25395,6 @@ "lineCount": 1 } }, - { - "code": "reportUnknownArgumentType", - "range": { - "startColumn": 34, - "endColumn": 64, - "lineCount": 1 - } - }, { "code": "reportUnknownVariableType", "range": { @@ -26478,42 +26350,42 @@ } }, { - "code": "reportUnknownMemberType", + "code": "reportUnknownVariableType", "range": { - "startColumn": 16, - "endColumn": 28, + "startColumn": 51, + "endColumn": 53, "lineCount": 1 } }, { - "code": "reportUnknownVariableType", + "code": "reportUnknownMemberType", "range": { - "startColumn": 69, - "endColumn": 71, + "startColumn": 16, + "endColumn": 28, "lineCount": 1 } }, { "code": "reportUnknownMemberType", "range": { - "startColumn": 12, - "endColumn": 31, + "startColumn": 48, + "endColumn": 61, "lineCount": 1 } }, { - "code": "reportUnknownMemberType", + "code": "reportUnknownArgumentType", "range": { - "startColumn": 40, - "endColumn": 53, + "startColumn": 48, + "endColumn": 64, "lineCount": 1 } }, { - "code": "reportUnknownArgumentType", + "code": "reportUnknownMemberType", "range": { - "startColumn": 40, - "endColumn": 56, + "startColumn": 12, + "endColumn": 31, "lineCount": 1 } }, @@ -26699,22 +26571,6 @@ "lineCount": 1 } }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 19, - "endColumn": 37, - "lineCount": 1 - } - }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 19, - "endColumn": 37, - "lineCount": 1 - } - }, { "code": "reportOptionalOperand", "range": { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e32cdfc..ceda35f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: sed -i /oct2py/d .test-conda-env-py3.yml sed -i /h5py/d .test-conda-env-py3.yml build_py_project_in_conda_env - python -m pip install basedpyright scipy-stubs + python -m pip install basedpyright scipy-stubs optype basedpyright pytest3: diff --git a/doc/conf.py b/doc/conf.py index 1f91fa2d..4e468b7e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -38,6 +38,11 @@ nitpick_ignore_regex = [ ["py:class", r".*VTKConnectivity"], + # NOTE: optype does not have Sphinx compatible documentation + ["py:class", r"onp.*"], + # NOTE: don't want to document these + ["py:class", r".*NodalAdjacencyLike"], + ["py:class", r".*FacialAdjacencyLike"], ] sphinxconfig_missing_reference_aliases = { @@ -56,6 +61,8 @@ "ObjectArray": "class:pytools.obj_array.ObjectArray", # modepy "ArrayF": "obj:modepy.typing.ArrayF", + "mp.FunctionSpace": "class:modepy.FunctionSpace", + "mp.Shape": "class:modepy.Shape", # arraycontext "Array": "class:arraycontext.Array", "ArrayContext": "class:arraycontext.ArrayContext", diff --git a/examples/mesh-to-tikz.py b/examples/mesh-to-tikz.py index e0c38bc8..dc8bae6f 100644 --- a/examples/mesh-to-tikz.py +++ b/examples/mesh-to-tikz.py @@ -9,7 +9,7 @@ FileSource("../test/blob-2d.step"), 2, order=order, force_ambient_dim=2, other_options=[ - "-string", "Mesh.CharacteristicLengthMax = %s;" % h] + "-string", f"Mesh.CharacteristicLengthMax = {h};"] ) print(mesh_to_tikz(mesh)) diff --git a/examples/simple-dg.py b/examples/simple-dg.py index 54573e81..174820e5 100644 --- a/examples/simple-dg.py +++ b/examples/simple-dg.py @@ -528,7 +528,7 @@ def rhs_wrapper(t, q): assert len(fields.u) == 1 logger.info("[%05d] t %.5e / %.5e norm %.5e", istep, t, t_final, actx_outer.to_numpy(flat_norm(fields.u, 2))) - vis.write_vtk_file("fld-wave-min-%04d.vtu" % istep, [ + vis.write_vtk_file(f"fld-wave-min-{istep:04d}.vtu", [ ("q", fields), ]) diff --git a/meshmode/discretization/connection/direct.py b/meshmode/discretization/connection/direct.py index ba8cd915..16257b1a 100644 --- a/meshmode/discretization/connection/direct.py +++ b/meshmode/discretization/connection/direct.py @@ -402,10 +402,10 @@ def _resample_matrix(self, actx: ArrayContext, to_group_index: int, if len(from_grp_basis_fcts) != nfrom_unit_nodes: from meshmode.discretization import NoninterpolatoryElementGroupError raise NoninterpolatoryElementGroupError( - "%s does not support interpolation because it is not " - "unisolvent (its unit node count does not match its " + f"{type(from_grp)} does not support interpolation because " + "it is not unisolvent (its unit node count does not match its " "number of basis functions). Using connections requires " - "the ability to interpolate." % type(from_grp).__name__) + "the ability to interpolate.") result = mp.resampling_matrix( from_grp_basis_fcts, diff --git a/meshmode/discretization/connection/modal.py b/meshmode/discretization/connection/modal.py index ac94c369..adf20bcb 100644 --- a/meshmode/discretization/connection/modal.py +++ b/meshmode/discretization/connection/modal.py @@ -236,9 +236,7 @@ def __call__(self, ary): else: raise NotImplementedError( "Don't know how to project from group types " - "%s to %s" % (grp.__class__.__name__, - mgrp.__class__.__name__) - ) + f"{grp.__class__} to {grp.__class__}") result_data.append(output) diff --git a/meshmode/discretization/connection/opposite_face.py b/meshmode/discretization/connection/opposite_face.py index 6917c776..06f54360 100644 --- a/meshmode/discretization/connection/opposite_face.py +++ b/meshmode/discretization/connection/opposite_face.py @@ -307,7 +307,7 @@ def get_map_jacobian(unit_nodes): niter += 1 if niter > 10: raise RuntimeError("Gauss-Newton (for finding opposite-face reference " - "coordinates) did not converge (residual: %g)" % max_resid) + f"coordinates) did not converge (residual: {max_resid})") raise AssertionError() diff --git a/meshmode/discretization/poly_element.py b/meshmode/discretization/poly_element.py index 00bb7d48..41a70e1a 100644 --- a/meshmode/discretization/poly_element.py +++ b/meshmode/discretization/poly_element.py @@ -723,8 +723,9 @@ def __call__(self, mesh_el_group: _MeshElementGroup) -> ElementGroupBase: :attr:`order`. """ if not isinstance(mesh_el_group, self.mesh_group_class): - raise TypeError("only mesh element groups of type '%s' " - "are supported" % self.mesh_group_class.__name__) + raise TypeError( + f"only mesh element groups of type {self.mesh_group_class} " + "are supported") return self.group_class(mesh_el_group, self.order) diff --git a/meshmode/discretization/visualization.py b/meshmode/discretization/visualization.py index cdb397fa..bc2f72eb 100644 --- a/meshmode/discretization/visualization.py +++ b/meshmode/discretization/visualization.py @@ -29,7 +29,7 @@ import logging from dataclasses import dataclass from functools import singledispatch -from typing import TYPE_CHECKING, Any, TypeAlias +from typing import TYPE_CHECKING, Any import numpy as np from typing_extensions import TypeIs, override @@ -56,14 +56,13 @@ if TYPE_CHECKING: from collections.abc import Iterator, Sequence - from numpy.typing import NDArray + import optype.numpy as onp from meshmode.discretization import Discretization, ElementGroupBase from meshmode.discretization.connection.direct import ( DiscretizationConnection, ) - logger = logging.getLogger(__name__) __doc__ = """ @@ -75,13 +74,11 @@ .. autofunction:: write_nodal_adjacency_vtk_file """ -IndexArray: TypeAlias = "NDArray[np.integer]" - # {{{ helpers def separate_by_real_and_imag( - names_and_fields: Sequence[tuple[str, NDArray[np.inexact]]], + names_and_fields: Sequence[tuple[str, ArrayOrContainerOrScalar]], real_only: bool, ) -> Iterator[tuple[str, ArrayOrContainerOrScalar]]: """ @@ -135,7 +132,8 @@ def _resample_to_numpy( *, stack: bool = False, by_group: bool = False - ) -> NDArray[Any] | ObjectArray[tuple[int, ...], NDArray[Any]]: + ) -> (onp.ArrayND[np.inexact] + | ObjectArray[tuple[int, ...], onp.ArrayND[np.inexact]]): """ :arg stack: if *True* object arrays are stacked into a single :class:`~numpy.ndarray`. @@ -231,7 +229,7 @@ class _VisConnectivityGroup: Starting index for subelements in :attr:`vis_connectivity`. """ - vis_connectivity: np.ndarray + vis_connectivity: onp.Array3D[np.integer[Any]] vtk_cell_type: int subelement_nr_base: int @@ -364,7 +362,8 @@ def tensor_cell_types(self) -> dict[int, int]: } def connectivity_for_element_group( - self, grp: ElementGroupBase) -> tuple[IndexArray, int]: + self, grp: ElementGroupBase + ) -> tuple[onp.Array3D[np.integer[Any]], int]: import modepy as mp from meshmode.mesh import ModepyElementGroup @@ -389,14 +388,14 @@ def connectivity_for_element_group( else: raise NotImplementedError("visualization for element groups " - "of type '%s'" % type(grp.mesh_el_group).__name__) + f"of type {type(grp.mesh_el_group)}") assert len(node_tuples) == grp.nunit_dofs return el_connectivity, vtk_cell_type @property @memoize_method - def cells(self) -> IndexArray: + def cells(self) -> onp.Array2D[np.integer[Any]]: return np.hstack([ vgrp.vis_connectivity.reshape(-1) for vgrp in self.groups ]) @@ -436,7 +435,7 @@ def groups(self) -> Sequence[_VisConnectivityGroup]: @property @memoize_method - def cell_types(self) -> IndexArray: + def cell_types(self) -> onp.Array1D[np.integer[Any]]: nsubelements = sum(vgrp.nsubelements for vgrp in self.groups) cell_types = np.empty(nsubelements, dtype=np.uint8) cell_types.fill(255) @@ -482,7 +481,8 @@ def tensor_cell_types(self) -> dict[int, int]: @override def connectivity_for_element_group( - self, grp: ElementGroupBase) -> tuple[IndexArray, int]: + self, grp: ElementGroupBase + ) -> tuple[onp.Array3D[np.integer[Any]], int]: from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup vtk_major, vtk_minor = self.version.split(".") @@ -495,9 +495,10 @@ def connectivity_for_element_group( node_tuples = vtk_lagrange_simplex_node_tuples( grp.dim, grp.order, vtk_version=vtk_version) + el_connectivity = np.array( vtk_lagrange_simplex_node_tuples_to_permutation(node_tuples), - dtype=np.intp).reshape((1, 1, -1)) + dtype=np.intp).reshape(1, 1, -1) vtk_cell_type = self.simplex_cell_types[grp.dim] @@ -511,20 +512,22 @@ def connectivity_for_element_group( grp.dim, grp.order, vtk_version=vtk_version) el_connectivity = np.array( vtk_lagrange_quad_node_tuples_to_permutation(node_tuples), - dtype=np.intp).reshape((1, 1, -1)) + dtype=np.intp).reshape(1, 1, -1) vtk_cell_type = self.tensor_cell_types[grp.dim] else: raise NotImplementedError("visualization for element groups " - "of type '%s'" % type(grp.mesh_el_group).__name__) + f"of type '{type(grp.mesh_el_group)}'") assert len(node_tuples) == grp.nunit_dofs return el_connectivity, vtk_cell_type @property @memoize_method - def cells(self) -> tuple[int, IndexArray, IndexArray]: + def cells(self) -> tuple[int, + onp.Array2D[np.integer[Any]], + onp.Array1D[np.integer[Any]]]: connectivity = np.hstack([ grp.vis_connectivity.reshape(-1) for grp in self.groups @@ -681,8 +684,8 @@ def show_scalar_in_mayavi(self, field, **kwargs): mlab.triangular_mesh(*args, **kwargs) else: - raise RuntimeError("meshes of bulk dimension %d are currently " - "unsupported" % self.vis_discr.dim) + raise RuntimeError(f"meshes of bulk dimension {self.vis_discr.dim} " + "are currently unsupported") if do_show: mlab.show() @@ -1114,7 +1117,7 @@ def create_dataset(grp, name, data, *, shape, offset): # {{{ xdmf @memoize_method - def _xdmf_nodes_numpy(self) -> ObjectArray1D[NDArray[np.inexact]]: + def _xdmf_nodes_numpy(self) -> ObjectArray1D[onp.Array2D[np.floating]]: actx = self.vis_discr._setup_actx return _resample_to_numpy( lambda x: x, self.vis_discr, @@ -1359,8 +1362,8 @@ def show_scalar_in_matplotlib_3d(self, field, **kwargs): ax.auto_scale_xyz(xt, yt, zt, had_data) else: - raise RuntimeError("meshes of bulk dimension %d are currently " - "unsupported" % self.vis_discr.dim) + raise RuntimeError(f"meshes of bulk dimension {self.vis_discr.dim} " + "are currently unsupported") if do_show: plt.show() @@ -1444,7 +1447,7 @@ def draw_curve(discr): color=color[igrp]) if artist_handles: - artist_handles[0].set_label("Group %d" % igrp) + artist_handles[0].set_label(f"Group {igrp}") # }}} @@ -1496,7 +1499,7 @@ def write_nodal_adjacency_vtk_file(file_name, mesh, if overwrite: os.remove(file_name) else: - raise FileExistsError("output file '%s' already exists" % file_name) + raise FileExistsError(f"output file '{file_name}' already exists") with open(file_name, "w") as outf: AppendedDataXMLGenerator(compressor)(grid).write(outf) diff --git a/meshmode/interop/firedrake/connection.py b/meshmode/interop/firedrake/connection.py index f24c93aa..a870d6db 100644 --- a/meshmode/interop/firedrake/connection.py +++ b/meshmode/interop/firedrake/connection.py @@ -163,36 +163,36 @@ def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): if not isinstance(discr, Discretization): raise TypeError("'discr' must be of type " "meshmode.discretization.Discretization, " - "not '%s'`." % type(discr)) + f"not {type(discr)}`") from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): raise TypeError("'fdrake_fspace' must be of type " "firedrake.functionspaceimpl.WithGeometry, " - "not '%s'." % type(fdrake_fspace)) + f"not {type(fdrake_fspace)}") if not isinstance(mm2fd_node_mapping, np.ndarray): raise TypeError("'mm2fd_node_mapping' must be of type " "numpy.ndarray, " - "not '%s'." % type(mm2fd_node_mapping)) + f"not {type(mm2fd_node_mapping)}") if not isinstance(group_nr, int) and group_nr is not None: raise TypeError("'group_nr' must be of type int or be " - "*None*, not of type '%s'." % type(group_nr)) + f"*None*, not of type '{type(group_nr)}'") # Convert group_nr to an integer if *None* if group_nr is None: if len(discr.groups) != 1: raise ValueError("'group_nr' is *None* but 'discr' " - "has '%s' != 1 groups." % len(discr.groups)) + f"has '{len(discr.groups)}' != 1 groups") group_nr = 0 # store element_grp as variable for convenience element_grp = discr.groups[group_nr] if group_nr < 0 or group_nr >= len(discr.groups): - raise ValueError("'group_nr' has value '%s', which an invalid " - "index into list 'discr.groups' of length '%s'." - % (group_nr, len(discr.groups))) + raise ValueError(f"'group_nr' has value '{group_nr}', which an invalid " + "index into list 'discr.groups' of length " + f"'{len(discr.groups)}'") if not isinstance(element_grp, InterpolatoryElementGroupBase): raise TypeError("'discr.groups[group_nr]' must be of type " "InterpolatoryElementGroupBase" - ", not '%s'." % type(element_grp)) + f", not '{type(element_grp)}'") if fdrake_fspace.ufl_element().family() != "Discontinuous Lagrange": raise TypeError("'fdrake_fspace.ufl_element().family()' must be" "'Discontinuous Lagrange', not " @@ -200,13 +200,12 @@ def __init__(self, discr, fdrake_fspace, mm2fd_node_mapping, group_nr=None): if mm2fd_node_mapping.shape != (element_grp.nelements, element_grp.nunit_dofs): raise ValueError("'mm2fd_node_mapping' must be of shape ", - "(%s,), not '%s'" - % ((discr.groups[group_nr].ndofs,), - mm2fd_node_mapping.shape)) + f"({discr.groups[group_nr].ndofs},), not " + f"'{mm2fd_node_mapping.shape}'") if mm2fd_node_mapping.dtype != fdrake_fspace.cell_node_list.dtype: raise ValueError("'mm2fd_node_mapping' must have dtype " - "%s, not '%s'" % (fdrake_fspace.cell_node_list.dtype, - mm2fd_node_mapping.dtype)) + f"{fdrake_fspace.cell_node_list.dtype}, not " + f"'{mm2fd_node_mapping.dtype}'") if np.size(np.unique(mm2fd_node_mapping)) != np.size(mm2fd_node_mapping): raise ValueError("'mm2fd_node_mapping' must have unique entries; " "no two meshmode nodes may be associated to the " @@ -275,8 +274,7 @@ def firedrake_fspace(self, shape=None): shape=shape) else: raise TypeError("'shape' must be *None*, an integer, " - " or a tuple of integers, not of type '%s'." - % type(shape)) + f" or a tuple of integers, not of type '{type(shape)}'") def _validate_function(self, function, function_name, shape=None, dtype=None): @@ -354,8 +352,8 @@ def check_dof_array(arr, arr_name): if isinstance(field, DOFArray): if shape is not None and shape != (): - raise ValueError("shape != () and '%s' is of type DOFArray" - " instead of np.ndarray." % field_name) + raise ValueError(f"shape != () and '{field_name}' is of type DOFArray" + " instead of np.ndarray.") check_dof_array(field, field_name) elif isinstance(field, np.ndarray) and field.dtype == object: if shape is not None and field.shape != shape: @@ -369,7 +367,7 @@ def check_dof_array(arr, arr_name): msg = type_err.args[0] prefix = f"'{field_name}' is a numpy array of shape " \ f"{field.shape}, which is interpreted as a mapping" \ - f" into a space of sahpe {field.shape}. For each " \ + f" into a space of shape {field.shape}. For each " \ r" multi-index *mi*, the *mi*\ th coordinate values " \ f" of '{field_name}' should be represented as a " \ f"DOFArray stored in '{field_name}[mi]'. If you are " \ @@ -381,7 +379,7 @@ def check_dof_array(arr, arr_name): raise TypeError(f"{prefix}\n{msg}") from None else: raise TypeError("'field' must be of type DOFArray or a numpy object " - "array of those, not '%s'." % type(field)) + f"array of those, not '{type(field)}'.") def from_firedrake(self, function, out=None, actx=None): """ @@ -442,7 +440,7 @@ def from_firedrake(self, function, out=None, actx=None): from arraycontext import ArrayContext if not isinstance(actx, ArrayContext): raise TypeError("If 'out' is *None*, 'actx' must be of type " - "ArrayContext, not '%s'." % type(actx)) + f"ArrayContext, not '{type(actx)}'") if fspace_shape == (): out = self.discr.empty(actx, dtype=function_data.dtype) else: @@ -651,15 +649,12 @@ def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, from firedrake.functionspaceimpl import WithGeometry if not isinstance(fdrake_fspace, WithGeometry): raise TypeError("'fdrake_fspace' must be of firedrake type " - "WithGeometry, not '%s'." - % type(fdrake_fspace)) + f"WithGeometry, not '{type(fdrake_fspace)}'.") ufl_elt = fdrake_fspace.ufl_element() if ufl_elt.family() != "Discontinuous Lagrange": - raise ValueError("the 'fdrake_fspace.ufl_element().family()' of " - "must be be " - "'Discontinuous Lagrange', not '%s'." - % ufl_elt.family()) + raise ValueError("the 'fdrake_fspace.ufl_element().family()' of must be " + f"'Discontinuous Lagrange', not '{ufl_elt.family()}'.") # If only converting a portion of the mesh near the boundary, get # *cells_to_use* as described in @@ -675,16 +670,14 @@ def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, if grp_factory is not None: if not isinstance(grp_factory, ElementGroupFactory): raise TypeError("'grp_factory' must inherit from " - "meshmode.discretization.ElementGroupFactory," - "but is instead of type " - "'%s'." % type(grp_factory)) + "meshmode.discretization.ElementGroupFactory, " + f"but is instead of type '{type(grp_factory)}'") if not issubclass(grp_factory.group_class, InterpolatoryElementGroupBase): raise TypeError("'grp_factory.group_class' must inherit from" - "meshmode.discretization." - "InterpolatoryElementGroupBase, but" - " is instead of type '%s'" - % type(grp_factory.group_class)) + "meshmode.discretization.InterpolatoryElementGroupBase, but" + f" is instead of type '{grp_factory.group_class}'") + # If not provided, make one else: grp_factory = default_simplex_group_factory( @@ -709,7 +702,7 @@ def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, "entry must be of type int") if bdy_id not in firedrake_bdy_ids: raise ValueError(f"Unrecognized boundary id {bdy_id}. Valid" - f" boundary ids: {firedrake_bdy_ids}.") + f" boundary ids: {firedrake_bdy_ids}") # String case, must be "on_boundary" elif isinstance(restrict_to_boundary, str): if restrict_to_boundary != "on_boundary": @@ -719,7 +712,7 @@ def build_connection_from_firedrake(actx, fdrake_fspace, grp_factory=None, else: raise TypeError(f"Invalid type {type(restrict_to_boundary)} for " "restrict_to_boundary. Must be an int, a tuple " - "of ints, or the string 'on_boundary'.") + "of ints, or the string 'on_boundary'") # Create to_discr to_discr = Discretization(actx, mm_mesh, grp_factory) @@ -780,8 +773,8 @@ def build_connection_to_firedrake(discr, group_nr=None, comm=None): """ if group_nr is None: if len(discr.groups) != 1: - raise ValueError("'group_nr' is *None*, but 'discr' has '%s' " - "!= 1 groups." % len(discr.groups)) + raise ValueError("'group_nr' is *None*, but 'discr' has " + f"'{len(discr.groups)}' != 1 groups.") group_nr = 0 el_group = discr.groups[group_nr] diff --git a/meshmode/interop/firedrake/mesh.py b/meshmode/interop/firedrake/mesh.py index 4350bfaa..5a24648a 100644 --- a/meshmode/interop/firedrake/mesh.py +++ b/meshmode/interop/firedrake/mesh.py @@ -588,7 +588,7 @@ def import_firedrake_mesh(fdrake_mesh, cells_to_use=None, if not isinstance(fdrake_mesh, MeshGeometry): raise TypeError("'fdrake_mesh_topology' must be an instance of " "firedrake.mesh.MeshGeometry, " - "not '%s'." % type(fdrake_mesh)) + f"not '{type(fdrake_mesh)}'.") if cells_to_use is not None: if not isinstance(cells_to_use, np.ndarray): @@ -841,7 +841,7 @@ def export_mesh_to_firedrake(mesh, group_nr=None, comm=None): """ if not isinstance(mesh, Mesh): raise TypeError("'mesh' must of type meshmode.mesh.Mesh," - " not '%s'." % type(mesh)) + f" not '{type(mesh)}'.") if group_nr is None: if len(mesh.groups) != 1: raise ValueError("'group_nr' is *None* but 'mesh' has " diff --git a/meshmode/mesh/__init__.py b/meshmode/mesh/__init__.py index 72276704..a0d000b4 100644 --- a/meshmode/mesh/__init__.py +++ b/meshmode/mesh/__init__.py @@ -28,6 +28,7 @@ from dataclasses import InitVar, dataclass, field, replace from functools import partial from typing import ( + TYPE_CHECKING, Any, ClassVar, Literal, @@ -38,6 +39,7 @@ import numpy as np import numpy.linalg as la +from typing_extensions import override import modepy as mp from pytools import memoize_method, module_getattr_for_deprecations @@ -45,6 +47,11 @@ from meshmode.mesh.tools import AffineMap, optional_array_equal +if TYPE_CHECKING: + import optype.numpy as onp + from numpy.typing import DTypeLike + + __doc__ = """ .. autoclass:: MeshElementGroup .. autoclass:: ModepyElementGroup @@ -87,7 +94,6 @@ .. autoclass:: BTAG_INDUCED_BOUNDARY """ - # {{{ element tags BoundaryTag: TypeAlias = Hashable @@ -137,22 +143,29 @@ class BTAG_PARTITION(BTAG_NO_BOUNDARY): # noqa: N801 another :class:`Mesh`. The part identifier of the adjacent mesh is given by ``part_id``. - .. attribute:: part_id + .. autoattribute:: part_id + .. automethod:: as_python .. versionadded:: 2017.1 """ + + part_id: PartID + def __init__(self, part_id: PartID) -> None: self.part_id = part_id + @override def __hash__(self) -> int: return hash((type(self), self.part_id)) + @override def __eq__(self, other: object) -> bool: if isinstance(other, BTAG_PARTITION): return self.part_id == other.part_id else: return False + @override def __repr__(self) -> str: return "<{}({})>".format(type(self).__name__, repr(self.part_id)) @@ -186,67 +199,24 @@ class BTAG_INDUCED_BOUNDARY(BTAG_NO_BOUNDARY): # noqa: N801 class MeshElementGroup(ABC): """A group of elements sharing a common reference element. - .. attribute:: order - - The maximum degree used for interpolation. The exact meaning depends - on the element type, e.g. for :class:`SimplexElementGroup` this is - the total degree. - - .. attribute:: dim - - The number of dimensions spanned by the element. - *Not* the ambient dimension, see :attr:`Mesh.ambient_dim` - for that. - - .. attribute:: nvertices - - Number of vertices in the reference element. - - .. attribute:: nfaces - - Number of faces of the reference element. - - .. attribute:: nunit_nodes - - Number of nodes on the reference element. - - .. attribute:: nelements - - Number of elements in the group. + .. autoattribute:: order + .. autoattribute:: vertex_indices + .. autoattribute:: nodes + .. autoattribute:: unit_nodes - .. attribute:: nnodes - - Total number of nodes in the group (equivalent to - ``nelements * nunit_nodes``). - - .. attribute:: vertex_indices - - An array of shape ``(nelements, nvertices)`` of (mesh-wide) - vertex indices. This can also be *None* to support the case where the - associated mesh does not have any :attr:`~Mesh.vertices`. - - .. attribute:: nodes - - An array of node coordinates with shape - ``(mesh.ambient_dim, nelements, nunit_nodes)``. - - .. attribute:: unit_nodes - - An array with shape ``(dim, nunit_nodes)`` of nodes on the reference - element. The coordinates :attr:`nodes` are a mapped version - of these reference nodes. - - .. attribute:: is_affine - - A :class:`bool` flag that is *True* if the local-to-global - parametrization of all the elements in the group is affine. + .. autoproperty:: dim + .. autoproperty:: nvertices + .. autoproperty:: nfaces + .. autoproperty:: nunit_nodes + .. autoproperty:: nelements + .. autoproperty:: nnodes + .. autoproperty:: is_affine Element groups can also be compared for equality using the following methods. Note that these are *very* expensive, as they compare all the :attr:`nodes`. .. automethod:: __eq__ - .. automethod:: __init__ The following abstract methods must be implemented by subclasses. @@ -257,34 +227,59 @@ class MeshElementGroup(ABC): """ order: int - vertex_indices: np.ndarray | None - nodes: np.ndarray - unit_nodes: np.ndarray + """"The maximum degree used for interpolation. The exact meaning depends on + the element type, e.g. for :class:`SimplexElementGroup` this is the total degree. + """ + vertex_indices: onp.Array2D[np.integer[Any]] | None + """An array of shape ``(nelements, nvertices)`` of (mesh-wide) vertex indices. + This can also be *None* to support the case where the associated mesh does + not have any :attr:`~Mesh.vertices`. + """ + nodes: onp.Array3D[np.floating[Any]] + """An array of node coordinates with shape + ``(mesh.ambient_dim, nelements, nunit_nodes)``. + """ + unit_nodes: onp.Array2D[np.floating[Any]] + """An array with shape ``(dim, nunit_nodes)`` of nodes on the reference + element. The coordinates :attr:`nodes` are a mapped version + of these reference nodes. + """ @property def dim(self) -> int: + """The number of dimensions spanned by the element. *Not* the ambient + dimension, see :attr:`Mesh.ambient_dim` for that. + """ return self.unit_nodes.shape[0] @property def nvertices(self) -> int: + """Number of vertices in the reference element.""" return self.vertex_unit_coordinates().shape[-1] @property def nfaces(self) -> int: + """Number of faces of the reference element.""" return len(self.face_vertex_indices()) @property def nunit_nodes(self) -> int: + """Number of nodes on the reference element.""" return self.unit_nodes.shape[-1] @property def nelements(self) -> int: + """Number of elements in the group.""" return self.nodes.shape[1] @property def nnodes(self) -> int: + """Total number of nodes in the group (equivalent to + ``nelements * nunit_nodes``). + """ return self.nelements * self.unit_nodes.shape[-1] + @override def __eq__(self, other: object) -> bool: if type(self) is not type(other): return False @@ -298,6 +293,9 @@ def __eq__(self, other: object) -> bool: @property def is_affine(self) -> bool: + """A :class:`bool` flag that is *True* if the local-to-global + parametrization of all the elements in the group is affine. + """ raise NotImplementedError @abstractmethod @@ -309,7 +307,7 @@ def face_vertex_indices(self) -> tuple[tuple[int, ...], ...]: """ @abstractmethod - def vertex_unit_coordinates(self) -> np.ndarray: + def vertex_unit_coordinates(self) -> onp.Array2D[np.floating[Any]]: """ :returns: an array of shape ``(nfaces, dim)`` with the unit coordinates of each vertex. @@ -331,6 +329,7 @@ def make_group(cls, *args: Any, **kwargs: Any) -> MeshElementGroup: # https://stackoverflow.com/a/13624858 class _classproperty(property): # noqa: N801 + @override def __get__(self, owner_self: Any, owner_cls: type | None = None) -> Any: assert self.fget is not None return self.fget(owner_cls) @@ -339,20 +338,20 @@ def __get__(self, owner_self: Any, owner_cls: type | None = None) -> Any: @dataclass(frozen=True, eq=False) class ModepyElementGroup(MeshElementGroup): """ - .. attribute:: modepy_shape_cls - - Must be set by subclasses to generate the correct shape and spaces - attributes for the group. - - .. attribute:: shape - .. attribute:: space + .. autoattribute:: shape_cls + .. autoattribute:: shape + .. autoattribute:: space """ shape_cls: ClassVar[type[mp.Shape]] + """Must be set by subclasses to generate the correct shape and spaces + attributes for the group. + """ shape: mp.Shape = field(repr=False) space: mp.FunctionSpace = field(repr=False) @property + @override def nvertices(self) -> int: return self.shape.nvertices # pylint: disable=no-member @@ -366,16 +365,17 @@ def face_vertex_indices(self) -> tuple[tuple[int, ...], ...]: return tuple(face.volume_vertex_indices for face in self._modepy_faces) @memoize_method - def vertex_unit_coordinates(self) -> np.ndarray: + def vertex_unit_coordinates(self) -> onp.Array2D[np.floating[Any]]: return mp.unit_vertices_for_shape(self.shape).T @classmethod + @override def make_group(cls, order: int, - vertex_indices: np.ndarray | None, - nodes: np.ndarray, + vertex_indices: onp.Array2D[np.integer[Any]] | None, + nodes: onp.Array3D[np.floating[Any]], *, - unit_nodes: np.ndarray | None = None, + unit_nodes: onp.Array2D[np.floating[Any]] | None = None, dim: int | None = None) -> ModepyElementGroup: if unit_nodes is None: @@ -388,6 +388,8 @@ def make_group(cls, if unit_nodes.shape[0] != dim: raise ValueError("'dim' does not match 'unit_nodes' dimension") + assert dim is not None + # pylint: disable=abstract-class-instantiated shape = cls.shape_cls(dim) space = mp.space_for_shape(shape, order) @@ -440,6 +442,7 @@ class TensorProductElementGroup(ModepyElementGroup): shape_cls: ClassVar[type[mp.Shape]] = mp.Hypercube @property + @override def is_affine(self) -> bool: # Tensor product mappings are generically bilinear. # FIXME: Are affinely mapped ones a 'juicy' enough special case? @@ -461,7 +464,7 @@ class NodalAdjacency: .. automethod:: __eq__ """ - neighbors_starts: np.ndarray + neighbors_starts: onp.Array1D[np.integer[Any]] """" ``element_id_t [nelements+1]`` @@ -470,13 +473,14 @@ class NodalAdjacency: :attr:`neighbors` which are adjacent to *iel*. """ - neighbors: np.ndarray + neighbors: onp.Array1D[np.integer[Any]] """ ``element_id_t []`` See :attr:`neighbors_starts`. """ + @override def __eq__(self, other: object) -> bool: if type(self) is not type(other): return False @@ -521,16 +525,16 @@ class FacialAdjacencyGroup: .. autoattribute:: igroup .. autoattribute:: elements .. autoattribute:: element_faces - """ - igroup: int - """ - The mesh element group number of this group. + .. automethod:: as_python """ - elements: np.ndarray - element_faces: np.ndarray + igroup: int + """The mesh element group number of this group.""" + elements: onp.Array1D[np.integer[Any]] + element_faces: onp.Array1D[np.integer[Any]] + @override def __eq__(self, other: object) -> bool: if type(self) is not type(other): return False @@ -572,39 +576,42 @@ class InteriorAdjacencyGroup(FacialAdjacencyGroup): .. autoattribute:: neighbors .. autoattribute:: neighbor_faces .. autoattribute:: aff_map + .. automethod:: __eq__ + .. automethod:: as_python """ ineighbor_group: int """ID of neighboring group, or *None* for boundary faces. If identical - to :attr:`igroup`, then this contains the self-connectivity in this - group.""" + to :attr:`igroup`, then this contains the self-connectivity in this group. + """ - elements: np.ndarray + elements: onp.Array1D[np.integer[Any]] """``element_id_t [nfagrp_elements]``. ``elements[i]`` gives the element number within :attr:`igroup` of the interior face.""" - element_faces: np.ndarray + element_faces: onp.Array1D[np.integer[Any]] """``face_id_t [nfagrp_elements]``. ``element_faces[i]`` gives the face - index of the interior face in element ``elements[i]``.""" + index of the interior face in element ``elements[i]``. + """ - neighbors: np.ndarray + neighbors: onp.Array1D[np.integer[Any]] """``element_id_t [nfagrp_elements]``. ``neighbors[i]`` gives the element number within :attr:`ineighbor_group` of the element opposite ``elements[i]``. """ - neighbor_faces: np.ndarray + neighbor_faces: onp.Array1D[np.integer[Any]] """``face_id_t [nfagrp_elements]``. ``neighbor_faces[i]`` gives the face index of the opposite face in element ``neighbors[i]`` """ aff_map: AffineMap - """ - An :class:`~meshmode.AffineMap` representing the mapping from the group's + """An :class:`~meshmode.AffineMap` representing the mapping from the group's faces to their corresponding neighbor faces. """ + @override def __eq__(self, other: object) -> bool: if not super().__eq__(other): return False @@ -618,6 +625,7 @@ def __eq__(self, other: object) -> bool: and np.array_equal(self.neighbor_faces, other.neighbor_faces) and self.aff_map == other.aff_map) + @override def as_python(self) -> str: if type(self) is not InteriorAdjacencyGroup: raise NotImplementedError(f"Not implemented for {type(self)}.") @@ -644,23 +652,26 @@ class BoundaryAdjacencyGroup(FacialAdjacencyGroup): .. autoattribute:: boundary_tag .. autoattribute:: elements .. autoattribute:: element_faces + + .. automethod:: as_python """ boundary_tag: BoundaryTag """"The boundary tag identifier of this group.""" - elements: np.ndarray + elements: onp.Array1D[np.integer[Any]] """" ``element_id_t [nfagrp_elements]``. ``elements[i]`` gives the element number within :attr:`igroup` of the boundary face. """ - element_faces: np.ndarray + element_faces: onp.Array1D[np.integer[Any]] """" ``face_id_t [nfagrp_elements]``. ``element_faces[i]`` gives the face index of the boundary face in element ``elements[i]``. """ + @override def __eq__(self, other: object) -> bool: if not super().__eq__(other): return False @@ -671,6 +682,7 @@ def __eq__(self, other: object) -> bool: and np.array_equal(self.elements, other.elements) and np.array_equal(self.element_faces, other.element_faces)) + @override def as_python(self) -> str: if type(self) is not BoundaryAdjacencyGroup: raise NotImplementedError(f"Not implemented for {type(self)}.") @@ -689,8 +701,7 @@ def as_python(self) -> str: @dataclass(frozen=True, eq=False) class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): """ - Describes inter-part adjacency information for one - :class:`MeshElementGroup`. + Describes inter-part adjacency information for one :class:`MeshElementGroup`. .. autoattribute:: igroup .. autoattribute:: boundary_tag @@ -701,6 +712,8 @@ class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): .. autoattribute:: neighbor_faces .. autoattribute:: aff_map + .. automethod:: as_python + .. versionadded:: 2017.1 """ @@ -717,7 +730,7 @@ class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): """The identifier of the neighboring part. """ - elements: np.ndarray + elements: onp.Array1D[np.integer[Any]] """Group-local element numbers. Element ``element_id_dtype elements[i]`` and face ``face_id_dtype element_faces[i]`` is connected to neighbor element @@ -725,12 +738,12 @@ class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): ``face_id_dtype neighbor_faces[i]``. """ - element_faces: np.ndarray + element_faces: onp.Array1D[np.integer[Any]] """``face_id_dtype element_faces[i]`` gives the face of ``element_id_dtype elements[i]`` that is connected to ``neighbors[i]``. """ - neighbors: np.ndarray + neighbors: onp.Array1D[np.integer[Any]] """``element_id_dtype neighbors[i]`` gives the volume element number within the neighboring part of the element connected to ``element_id_dtype elements[i]`` (which is a boundary element index). Use @@ -739,7 +752,7 @@ class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): element of the group. """ - neighbor_faces: np.ndarray + neighbor_faces: onp.Array1D[np.integer[Any]] """``face_id_dtype global_neighbor_faces[i]`` gives face index within the neighboring part of the face connected to ``element_id_dtype elements[i]`` """ @@ -749,6 +762,7 @@ class InterPartAdjacencyGroup(BoundaryAdjacencyGroup): faces to their corresponding neighbor faces. """ + @override def __eq__(self, other: object) -> bool: if not super().__eq__(other): return False @@ -760,6 +774,7 @@ def __eq__(self, other: object) -> bool: and np.array_equal(self.neighbor_faces, other.neighbor_faces) and self.aff_map == other.aff_map) + @override def as_python(self) -> str: if type(self) is not InterPartAdjacencyGroup: raise NotImplementedError(f"Not implemented for {type(self)}.") @@ -779,9 +794,8 @@ def as_python(self) -> str: # {{{ mesh -DTypeLike = np.dtype | np.generic NodalAdjacencyLike = ( - Literal[False] | Iterable[np.ndarray] | NodalAdjacency + Literal[False] | Iterable["onp.Array1D[np.integer[Any]]"] | NodalAdjacency ) FacialAdjacencyLike = ( Literal[False] | Sequence[Sequence[FacialAdjacencyGroup]] @@ -963,11 +977,18 @@ def is_mesh_consistent( def make_mesh( - vertices: np.ndarray | None, + vertices: onp.Array2D[np.floating[Any]] | None, groups: Iterable[MeshElementGroup], *, - nodal_adjacency: NodalAdjacencyLike | None = None, - facial_adjacency_groups: FacialAdjacencyLike | None = None, + nodal_adjacency: ( + Literal[False] + | Iterable[onp.Array1D[np.integer[Any]]] + | NodalAdjacency + | None) = None, + facial_adjacency_groups: ( + Literal[False] + | Sequence[Sequence[FacialAdjacencyGroup]] + | None) = None, is_conforming: bool | None = None, # dtypes vertex_id_dtype: DTypeLike = np.dtype("int32"), # noqa: B008 @@ -1142,7 +1163,7 @@ class Mesh: groups: tuple[MeshElementGroup, ...] """A tuple of :class:`MeshElementGroup` instances.""" - vertices: np.ndarray | None + vertices: onp.Array2D[np.floating[Any]] | None """*None* or an array of vertex coordinates with shape *(ambient_dim, nvertices)*. If *None*, vertices are not known for this mesh and no adjacency information can be constructed. @@ -1154,15 +1175,15 @@ class Mesh: neither of the two is known. """ - vertex_id_dtype: np.dtype + vertex_id_dtype: np.dtype[np.integer[Any]] """The :class:`~numpy.dtype` used to index into the vertex array.""" - element_id_dtype: np.dtype + element_id_dtype: np.dtype[np.integer[Any]] """The :class:`~numpy.dtype` used to index into the element array (relative to each group). """ - face_id_dtype: np.dtype + face_id_dtype: np.dtype[np.integer[Any]] """The :class:`~numpy.dtype` used to index element faces (relative to each element). """ @@ -1196,7 +1217,7 @@ class Mesh: def __init__( self, - vertices: np.ndarray | None, + vertices: onp.Array2D[np.floating] | None, groups: Iterable[MeshElementGroup], is_conforming: bool | None = None, vertex_id_dtype: DTypeLike = np.dtype("int32"), # noqa: B008 @@ -1351,7 +1372,7 @@ def nelements(self) -> int: @property @memoize_method - def base_element_nrs(self) -> np.ndarray: + def base_element_nrs(self) -> onp.Array1D[np.integer[Any]]: """An array of size ``(len(groups),)`` of starting element indices for each group in the mesh. """ @@ -1359,14 +1380,14 @@ def base_element_nrs(self) -> np.ndarray: @property @memoize_method - def base_node_nrs(self) -> np.ndarray: + def base_node_nrs(self) -> onp.Array1D[np.integer[Any]]: """An array of size ``(len(groups),)`` of starting node indices for each group in the mesh. """ return np.cumsum([0] + [grp.nnodes for grp in self.groups[:-1]]) @property - def vertex_dtype(self) -> np.dtype: + def vertex_dtype(self) -> np.dtype[np.floating[Any]]: """The :class:`~numpy.dtype` of the :attr:`~Mesh.vertices` array, if any.""" if self.vertices is None: from meshmode import DataUnavailableError @@ -1471,6 +1492,7 @@ def facial_adjacency_groups( return facial_adjacency_groups + @override def __eq__(self, other: object) -> bool: """Compare two meshes for equality. @@ -1510,7 +1532,9 @@ def __eq__(self, other: object) -> bool: # {{{ node-vertex consistency test -def _mesh_group_node_vertex_error(mesh: Mesh, mgrp: MeshElementGroup) -> np.ndarray: +def _mesh_group_node_vertex_error( + mesh: Mesh, mgrp: MeshElementGroup + ) -> onp.Array3D[np.floating[Any]]: if isinstance(mgrp, ModepyElementGroup): basis = mp.basis_for_space(mgrp.space, mgrp.shape).functions else: @@ -1548,7 +1572,7 @@ def _test_group_node_vertex_consistency_resampling( np.max(np.abs(per_vertex_errors), axis=-1), axis=0) if tol is None: - tol = 1e3 * np.finfo(per_element_vertex_errors.dtype).eps + tol = float(1e3 * np.finfo(per_element_vertex_errors.dtype).eps) grp_vertices = mesh.vertices[:, mgrp.vertex_indices] @@ -1593,7 +1617,7 @@ def _compute_nodal_adjacency_from_vertices(mesh: Mesh) -> NodalAdjacency: raise ValueError("unable to compute nodal adjacency without vertices") _, nvertices = mesh.vertices.shape - vertex_to_element: list[list[int]] = [[] for i in range(nvertices)] + vertex_to_element: list[list[int]] = [[] for _ in range(nvertices)] for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): if grp.vertex_indices is None: @@ -1603,7 +1627,7 @@ def _compute_nodal_adjacency_from_vertices(mesh: Mesh) -> NodalAdjacency: for ivertex in grp.vertex_indices[iel_grp]: vertex_to_element[ivertex].append(base_element_nr + iel_grp) - element_to_element: list[set[int]] = [set() for i in range(mesh.nelements)] + element_to_element: list[set[int]] = [set() for _ in range(mesh.nelements)] for base_element_nr, grp in zip(mesh.base_element_nrs, mesh.groups, strict=True): assert grp.vertex_indices is not None @@ -1640,21 +1664,17 @@ class _FaceIDs: Data structure for storage of a list of face identifiers (group, element, face). Each attribute is a :class:`numpy.ndarray` of shape ``(nfaces,)``. - .. attribute:: groups - - The index of the group containing the face. - - .. attribute:: elements - - The group-relative index of the element containing the face. - - .. attribute:: faces - - The element-relative index of face. + .. autoattribute:: groups + .. autoattribute:: elements + .. autoattribute:: faces """ - groups: np.ndarray - elements: np.ndarray - faces: np.ndarray + + groups: onp.Array1D[np.integer[Any]] + """The index of the group containing the face.""" + elements: onp.Array1D[np.integer[Any]] + """The group-relative index of the element containing the face.""" + faces: onp.Array1D[np.integer[Any]] + """The element-relative index of face.""" def _concatenate_face_ids(face_ids_list: Sequence[_FaceIDs]) -> _FaceIDs: @@ -1675,8 +1695,10 @@ def _assert_not_none(val: T | None) -> T: def _match_faces_by_vertices( groups: Sequence[MeshElementGroup], face_ids: _FaceIDs, - vertex_index_map_func: Callable[[np.ndarray], np.ndarray] | None = None - ) -> np.ndarray: + vertex_index_map_func: ( + Callable[[onp.Array1D[np.integer[Any]]], + onp.Array1D[np.integer[Any]]] | None) = None + ) -> onp.Array1D[np.integer[Any]]: """ Return matching faces in *face_ids* (expressed as pairs of indices into *face_ids*), where two faces match if they have the same vertices. @@ -1695,9 +1717,13 @@ def _match_faces_by_vertices( in *face_ids*. """ if vertex_index_map_func is None: - def vertex_index_map_func(vertices: np.ndarray) -> np.ndarray: + def default_vertex_index_map_func( + vertices: onp.Array1D[np.integer[Any]] + ) -> onp.Array1D[np.integer[Any]]: return vertices + vertex_index_map_func = default_vertex_index_map_func + from pytools import single_valued vertex_id_dtype = single_valued( _assert_not_none(grp.vertex_indices).dtype for grp in groups) @@ -1735,8 +1761,8 @@ def vertex_index_map_func(vertices: np.ndarray) -> np.ndarray: def _compute_facial_adjacency_from_vertices( groups: Sequence[MeshElementGroup], - element_id_dtype: np.dtype[np.integer], - face_id_dtype: np.dtype[np.integer], + element_id_dtype: np.dtype[np.integer[Any]], + face_id_dtype: np.dtype[np.integer[Any]], face_vertex_indices_to_tags: Mapping[ frozenset[int], Sequence[BoundaryTag]] | None = None, ) -> Sequence[Sequence[FacialAdjacencyGroup]]: @@ -1744,7 +1770,7 @@ def _compute_facial_adjacency_from_vertices( return [] if face_vertex_indices_to_tags is not None: - boundary_tags = { + boundary_tags: set[BoundaryTag] = { tag for tags in face_vertex_indices_to_tags.values() for tag in tags @@ -1756,19 +1782,20 @@ def _compute_facial_adjacency_from_vertices( # Match up adjacent faces according to their vertex indices - face_ids_per_group = [] + face_ids_per_group: list[_FaceIDs] = [] for igrp, grp in enumerate(groups): indices = np.indices((grp.nfaces, grp.nelements), dtype=element_id_dtype) face_ids_per_group.append(_FaceIDs( groups=np.full(grp.nelements * grp.nfaces, igrp), elements=indices[1].flatten(), faces=indices[0].flatten().astype(face_id_dtype))) + face_ids = _concatenate_face_ids(face_ids_per_group) face_index_pairs = _match_faces_by_vertices(groups, face_ids) - del igrp - del grp + del igrp # pyright: ignore[reportPossiblyUnboundVariable] + del grp # pyright: ignore[reportPossiblyUnboundVariable] # Get ((grp#, elem#, face#), (neighbor grp#, neighbor elem#, neighbor face#)) # for every face (both ways) @@ -1800,7 +1827,7 @@ def _compute_facial_adjacency_from_vertices( # {{{ build facial_adjacency_groups data structure - facial_adjacency_groups = [] + facial_adjacency_groups: list[list[FacialAdjacencyGroup]] = [] for igrp, grp in enumerate(groups): assert grp.vertex_indices is not None @@ -1882,8 +1909,8 @@ def _merge_boundary_adjacency_groups( igrp: int, bdry_grps: Sequence[BoundaryAdjacencyGroup], merged_btag: BoundaryTag, - element_id_dtype: np.dtype, - face_id_dtype: np.dtype, + element_id_dtype: np.dtype[np.integer[Any]], + face_id_dtype: np.dtype[np.integer[Any]], ) -> BoundaryAdjacencyGroup: """ Create a new :class:`~meshmode.mesh.BoundaryAdjacencyGroup` containing all of @@ -1922,8 +1949,8 @@ def _merge_boundary_adjacency_groups( def _complete_facial_adjacency_groups( facial_adjacency_groups: Sequence[Sequence[FacialAdjacencyGroup]], - element_id_dtype: np.dtype, - face_id_dtype: np.dtype + element_id_dtype: np.dtype[np.integer[Any]], + face_id_dtype: np.dtype[np.integer[Any]] ) -> tuple[tuple[FacialAdjacencyGroup, ...], ...]: """ Add :class:`~meshmode.mesh.BoundaryAdjacencyGroup` instances for @@ -1986,7 +2013,8 @@ def _boundary_tag_as_python(boundary_tag: BoundaryTag) -> str: return boundary_tag.as_python() -def _numpy_array_as_python(array: np.ndarray | None) -> str: +def _numpy_array_as_python( + array: np.ndarray[tuple[int, ...], np.dtype[Any]] | None) -> str: if array is not None: return "np.array({}, dtype=np.{})".format( repr(array.tolist()), @@ -2002,8 +2030,9 @@ def _affine_map_as_python(aff_map: AffineMap) -> str: def as_python(mesh: Mesh, function_name: str = "make_mesh") -> str: - """Return a snippet of Python code (as a string) that will - recreate the mesh given as an input parameter. + """ + :returns: a snippet of Python code (as a string) that will recreate the + mesh given as an input parameter. """ from pytools.py_codegen import Indentation, PythonCodeGenerator @@ -2026,24 +2055,21 @@ def as_python(mesh: Mesh, function_name: str = "make_mesh") -> str: """) - cg("def %s():" % function_name) + cg(f"def {function_name}():") with Indentation(cg): cg("vertices = " + _numpy_array_as_python(mesh.vertices)) cg("") cg("groups = []") cg("") for group in mesh.groups: - cg("import %s" % type(group).__module__) + cg(f"import {type(group).__module__}") cg("groups.append({}.{}.make_group(".format( type(group).__module__, type(group).__name__)) - cg(" order=%s," % group.order) - cg(" vertex_indices=%s," - % _numpy_array_as_python(group.vertex_indices)) - cg(" nodes=%s," - % _numpy_array_as_python(group.nodes)) - cg(" unit_nodes=%s))" - % _numpy_array_as_python(group.unit_nodes)) + cg(f" order={group.order},") + cg(f" vertex_indices={_numpy_array_as_python(group.vertex_indices)},") + cg(f" nodes={_numpy_array_as_python(group.nodes)},") + cg(f" unit_nodes={_numpy_array_as_python(group.unit_nodes)}))") # {{{ facial adjacency groups @@ -2058,13 +2084,13 @@ def as_python(mesh: Mesh, function_name: str = "make_mesh") -> str: cg("])") else: - cg("facial_adjacency_groups = %r" % mesh._facial_adjacency_groups) + cg(f"facial_adjacency_groups = {mesh._facial_adjacency_groups!r}") # }}} cg("return mm_make_mesh(vertices, groups, skip_tests=True,") - cg(" vertex_id_dtype=np.%s," % mesh.vertex_id_dtype.name) - cg(" element_id_dtype=np.%s," % mesh.element_id_dtype.name) + cg(f" vertex_id_dtype=np.{mesh.vertex_id_dtype.name},") + cg(f" element_id_dtype=np.{mesh.element_id_dtype.name},") if isinstance(mesh._nodal_adjacency, NodalAdjacency): el_con_str = "({}, {})".format( @@ -2076,9 +2102,9 @@ def as_python(mesh: Mesh, function_name: str = "make_mesh") -> str: else: el_con_str = repr(mesh._nodal_adjacency) - cg(" nodal_adjacency=%s," % el_con_str) + cg(f" nodal_adjacency={el_con_str},") cg(" facial_adjacency_groups=facial_adjacency_groups,") - cg(" is_conforming=%s)" % repr(mesh.is_conforming)) + cg(f" is_conforming={mesh.is_conforming!r})") # FIXME: Handle facial adjacency, boundary tags @@ -2157,7 +2183,9 @@ def check_bc_coverage( if isinstance(fagrp, BoundaryAdjacencyGroup) and fagrp.boundary_tag in boundary_tags] - def get_bdry_counts(bdry_grp: BoundaryAdjacencyGroup) -> np.ndarray: + def get_bdry_counts( + bdry_grp: BoundaryAdjacencyGroup + ) -> np.ndarray[tuple[int, int], np.dtype[np.integer[Any]]]: counts = np.full((grp.nfaces, grp.nelements), 0) # noqa: B023 counts[bdry_grp.element_faces, bdry_grp.elements] += 1 return counts @@ -2215,8 +2243,7 @@ def is_affine_simplex_group( abs_tol = 1.0e-13 if not isinstance(group, SimplexElementGroup): - raise TypeError("expected a 'SimplexElementGroup' not '%s'" % - type(group).__name__) + raise TypeError(f"expected a 'SimplexElementGroup': {type(group)}") if group.nelements == 0: # All zero of them are affine! :) @@ -2232,7 +2259,8 @@ def is_affine_simplex_group( # construct all second derivative matrices (including cross terms) from itertools import product - mats = [] + + mats: list[np.ndarray[tuple[int, int], np.dtype[np.floating[Any]]]] = [] for n in product(range(group.dim), repeat=2): if n[0] > n[1]: continue diff --git a/meshmode/mesh/generation.py b/meshmode/mesh/generation.py index dc420915..085191a4 100644 --- a/meshmode/mesh/generation.py +++ b/meshmode/mesh/generation.py @@ -1532,7 +1532,7 @@ def generate_box_mesh( for box_face in box_faces} else: - raise NotImplementedError("box meshes of dimension %d" % dim) + raise NotImplementedError(f"box meshes of dimension {dim}") grp = make_group_from_vertices( vertices.reshape(dim, product(vertices.shape[1:])), el_vertices, order, diff --git a/meshmode/mesh/processing.py b/meshmode/mesh/processing.py index a589a795..ad7a3062 100644 --- a/meshmode/mesh/processing.py +++ b/meshmode/mesh/processing.py @@ -25,7 +25,7 @@ from dataclasses import dataclass, replace from functools import reduce -from typing import TYPE_CHECKING, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast from warnings import warn import numpy as np @@ -52,8 +52,7 @@ if TYPE_CHECKING: from collections.abc import Callable, Hashable, Mapping, Sequence - from numpy.typing import NDArray - + import optype.numpy as onp __doc__ = """ .. autoclass:: BoundaryPairMapping @@ -79,7 +78,7 @@ def find_group_indices( groups: Sequence[MeshElementGroup], - meshwide_elems: NDArray[np.integer]) -> NDArray[np.integer]: + meshwide_elems: onp.Array1D[np.integer[Any]]) -> onp.Array1D[np.integer[Any]]: """ :arg groups: A list of :class:`~meshmode.mesh.MeshElementGroup` instances that contain *meshwide_elems*. @@ -101,9 +100,9 @@ def find_group_indices( def _compute_global_elem_to_part_elem( nelements: int, - part_id_to_elements: Mapping[PartID, NDArray[np.integer]], + part_id_to_elements: Mapping[PartID, onp.Array1D[np.integer[Any]]], part_id_to_part_index: Mapping[PartID, int], - element_id_dtype: np.dtype[np.integer]) -> NDArray[np.integer]: + element_id_dtype: np.dtype[np.integer[Any]]) -> onp.Array2D[np.integer[Any]]: """ Create a map from global element index to part-wide element index for a set of parts. @@ -131,9 +130,9 @@ def _compute_global_elem_to_part_elem( def _filter_mesh_groups( mesh: Mesh, - selected_elements: NDArray[np.integer], - vertex_id_dtype: np.dtype[np.integer], - ) -> tuple[list[MeshElementGroup], NDArray[np.integer]]: + selected_elements: onp.Array1D[np.integer[Any]], + vertex_id_dtype: np.dtype[np.integer[Any]], + ) -> tuple[list[MeshElementGroup], onp.Array1D[np.integer[Any]]]: """ Create new mesh groups containing a selected subset of elements. @@ -151,11 +150,12 @@ def _filter_mesh_groups( # {{{ find filtered_group_elements group_elem_starts = [ - cast("NDArray[np.integer]", np.searchsorted(selected_elements, base_element_nr)) + cast("onp.Array1D[np.integer[Any]]", + np.searchsorted(selected_elements, base_element_nr)) for base_element_nr in mesh.base_element_nrs ] + [len(selected_elements)] - filtered_group_elements: list[NDArray[np.integer]] = [] + filtered_group_elements: list[onp.Array1D[np.integer[Any]]] = [] for igrp in range(len(mesh.groups)): start_idx, end_idx = group_elem_starts[igrp:igrp+2] @@ -178,7 +178,7 @@ def _filter_mesh_groups( required_vertex_indices, new_vertex_indices_flat = np.unique( filtered_vertex_indices_flat, return_inverse=True) - new_vertex_indices: list[NDArray[np.integer]] = [] + new_vertex_indices: list[onp.Array1D[np.integer[Any]]] = [] start_idx = 0 for filtered_indices in filtered_vertex_indices: end_idx = start_idx + filtered_indices.size @@ -202,7 +202,7 @@ def _filter_mesh_groups( def _get_connected_parts( mesh: Mesh, part_id_to_part_index: Mapping[PartID, int], - global_elem_to_part_elem: NDArray[np.integer], + global_elem_to_part_elem: onp.Array2D[np.integer[Any]], self_part_id: PartID) -> Sequence[PartID]: """ Find the parts that are connected to the current part. @@ -254,7 +254,7 @@ def _get_connected_parts( def _create_self_to_self_adjacency_groups( mesh: Mesh, - global_elem_to_part_elem: NDArray[np.integer], + global_elem_to_part_elem: onp.Array2D[np.integer[Any]], self_part_index: int, self_mesh_groups: Sequence[MeshElementGroup], self_mesh_group_elem_base: Sequence[int]) -> list[list[InteriorAdjacencyGroup]]: @@ -324,7 +324,7 @@ def _create_self_to_self_adjacency_groups( def _create_self_to_other_adjacency_groups( mesh: Mesh, part_id_to_part_index: Mapping[PartID, int], - global_elem_to_part_elem: NDArray[np.integer], + global_elem_to_part_elem: onp.Array2D[np.integer[Any]], self_part_id: PartID, self_mesh_groups: Sequence[MeshElementGroup], self_mesh_group_elem_base: Sequence[int], @@ -406,7 +406,7 @@ def _create_self_to_other_adjacency_groups( def _create_boundary_groups( mesh: Mesh, - global_elem_to_part_elem: NDArray[np.integer], + global_elem_to_part_elem: onp.Array2D[np.integer[Any]], self_part_index: PartID, self_mesh_groups: Sequence[MeshElementGroup], self_mesh_group_elem_base: Sequence[int]) -> list[list[BoundaryAdjacencyGroup]]: @@ -464,7 +464,7 @@ def _create_boundary_groups( def _get_mesh_part( mesh: Mesh, - part_id_to_elements: Mapping[PartID, NDArray[np.integer]], + part_id_to_elements: Mapping[PartID, onp.Array1D[np.integer[Any]]], self_part_id: PartID) -> Mesh: """ :arg mesh: A :class:`~meshmode.mesh.Mesh` to be partitioned. @@ -548,7 +548,7 @@ def _gather_grps(igrp: int) -> list[FacialAdjacencyGroup]: def partition_mesh( mesh: Mesh, - part_id_to_elements: Mapping[PartID, NDArray[np.integer]], + part_id_to_elements: Mapping[PartID, onp.Array1D[np.integer[Any]]], return_parts: Sequence[PartID] | None = None) -> Mapping[PartID, Mesh]: """ :arg mesh: A :class:`~meshmode.mesh.Mesh` to be partitioned. @@ -574,8 +574,8 @@ def partition_mesh( # {{{ orientations def find_volume_mesh_element_group_orientation( - vertices: NDArray[np.floating], - grp: MeshElementGroup) -> NDArray[np.floating]: + vertices: onp.Array2D[np.floating[Any]], + grp: MeshElementGroup) -> onp.Array1D[np.floating[Any]]: """ :returns: a positive floating point number for each positively oriented element, and a negative floating point number for @@ -593,13 +593,13 @@ def find_volume_mesh_element_group_orientation( # (ambient_dim, nelements, nvertices) my_vertices = vertices[:, grp.vertex_indices] - def evec(i: int) -> NDArray[np.floating]: + def evec(i: int) -> onp.Array1D[np.floating[Any]]: """Make the i-th unit vector.""" result = np.zeros(grp.dim) result[i] = 1 return result - def unpack_single(ary: NDArray[np.floating] | None) -> int: + def unpack_single(ary: onp.ArrayND[np.floating[Any]] | None) -> int: assert ary is not None item, = ary return item @@ -647,7 +647,7 @@ def unpack_single(ary: NDArray[np.floating] | None) -> int: def find_volume_mesh_element_orientations( mesh: Mesh, *, - tolerate_unimplemented_checks: bool = False) -> NDArray[np.floating]: + tolerate_unimplemented_checks: bool = False) -> onp.Array1D[np.floating[Any]]: """Return a positive floating point number for each positively oriented element, and a negative floating point number for each negatively oriented element. @@ -684,9 +684,9 @@ def find_volume_mesh_element_orientations( def get_simplex_element_flip_matrix( order: int, - unit_nodes: NDArray[np.floating], + unit_nodes: onp.Array2D[np.floating[Any]], permutation: tuple[int, ...] | None = None, - ) -> tuple[NDArray[np.floating], NDArray[np.integer]]: + ) -> tuple[onp.Array2D[np.floating[Any]], onp.Array1D[np.integer[Any]]]: """ Generate a resampling matrix that corresponds to a permutation of the barycentric coordinates being applied. @@ -745,7 +745,7 @@ def get_simplex_element_flip_matrix( def _get_tensor_product_element_flip_matrix_and_vertex_permutation( grp: TensorProductElementGroup, - ) -> tuple[NDArray[np.floating], NDArray[np.integer]]: + ) -> tuple[onp.Array2D[np.floating[Any]], onp.Array1D[np.integer[Any]]]: unit_flip_matrix = np.eye(grp.dim) unit_flip_matrix[0, 0] = -1 @@ -780,9 +780,9 @@ def _get_tensor_product_element_flip_matrix_and_vertex_permutation( def flip_element_group( - vertices: NDArray[np.floating], + vertices: onp.Array2D[np.floating[Any]], grp: MeshElementGroup, - grp_flip_flags: NDArray[np.bool]) -> MeshElementGroup: + grp_flip_flags: onp.Array1D[np.bool]) -> MeshElementGroup: from meshmode.mesh import SimplexElementGroup, TensorProductElementGroup if isinstance(grp, SimplexElementGroup): @@ -817,7 +817,7 @@ def flip_element_group( def perform_flips( mesh: Mesh, - flip_flags: NDArray[np.bool], + flip_flags: onp.Array1D[np.bool], skip_tests: bool = False) -> Mesh: """ :arg flip_flags: A :class:`numpy.ndarray` with @@ -851,7 +851,9 @@ def perform_flips( # {{{ bounding box -def find_bounding_box(mesh: Mesh) -> tuple[NDArray[np.floating], NDArray[np.floating]]: +def find_bounding_box( + mesh: Mesh + ) -> tuple[onp.Array1D[np.floating[Any]], onp.Array1D[np.floating[Any]]]: """ :return: a tuple *(min, max)*, each consisting of a :class:`numpy.ndarray` indicating the minimal and maximal extent of the geometry along each axis. @@ -924,8 +926,8 @@ def merge_disjoint_meshes( and np.array_equal(x.unit_nodes, y.unit_nodes) )) - group_vertex_indices: list[NDArray[np.integer]] = [] - group_nodes: list[NDArray[np.floating]] = [] + group_vertex_indices: list[onp.Array2D[np.integer[Any]]] = [] + group_nodes: list[onp.Array3D[np.floating[Any]]] = [] for mesh, vert_base in zip(meshes, vert_bases, strict=True): for group in mesh.groups: assert group.vertex_indices is not None @@ -967,7 +969,7 @@ def merge_disjoint_meshes( def split_mesh_groups( mesh: Mesh, - element_flags: NDArray[np.integer], + element_flags: onp.Array1D[np.integer[Any]], return_subgroup_mapping: bool = False, ) -> Mesh | tuple[Mesh, dict[tuple[int, int], int]]: """Split all the groups in *mesh* according to the values of @@ -1069,13 +1071,15 @@ def _get_boundary_face_ids(mesh: Mesh, btag: Hashable) -> _FaceIDs: return _concatenate_face_ids(face_ids_per_boundary_group) -def _get_face_vertex_indices(mesh: Mesh, face_ids: _FaceIDs) -> NDArray[np.integer]: +def _get_face_vertex_indices( + mesh: Mesh, face_ids: _FaceIDs + ) -> onp.Array3D[np.integer[Any]]: max_face_vertices = max( len(ref_fvi) for grp in mesh.groups for ref_fvi in grp.face_vertex_indices()) - face_vertex_indices_per_group: list[NDArray[np.integer]] = [] + face_vertex_indices_per_group: list[onp.Array2D[np.integer[Any]]] = [] for igrp, grp in enumerate(mesh.groups): assert grp.vertex_indices is not None @@ -1316,7 +1320,8 @@ def glue_mesh_boundaries( # {{{ map def map_mesh(mesh: Mesh, - f: Callable[[NDArray[np.floating]], NDArray[np.floating]]) -> Mesh: + f: Callable[[onp.Array2D[np.floating[Any]]], + onp.Array2D[np.floating[Any]]]) -> Mesh: """Apply the map *f* to the mesh. *f* needs to accept and return arrays of shape ``(ambient_dim, npoints)``.""" @@ -1366,8 +1371,8 @@ def map_mesh(mesh: Mesh, def affine_map( mesh: Mesh, - A: np.generic | NDArray[np.floating] | None = None, - b: np.generic | NDArray[np.floating] | None = None) -> Mesh: + A: np.generic | onp.Array2D[np.floating[Any]] | None = None, + b: np.generic | onp.Array1D[np.floating[Any]] | 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): @@ -1464,7 +1469,8 @@ def compute_new_map(old_map: AffineMap) -> AffineMap: def _get_rotation_matrix_from_angle_and_axis( - theta: float, axis: NDArray[np.floating]) -> NDArray[np.floating]: + theta: float, axis: onp.Array1D[np.floating[Any]] + ) -> onp.Array2D[np.floating[Any]]: # https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle cos_t = np.cos(theta) sin_t = np.sin(theta) @@ -1488,7 +1494,7 @@ def _get_rotation_matrix_from_angle_and_axis( def rotate_mesh_around_axis( mesh: Mesh, *, theta: float, - axis: NDArray[np.floating] | None = None) -> Mesh: + axis: onp.Array1D[np.floating[Any]] | 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 @@ -1517,7 +1523,7 @@ def rotate_mesh_around_axis( def make_mesh_grid( mesh: Mesh, *, shape: tuple[int, ...], - offset: tuple[NDArray[np.floating], ...] | None = None, + offset: tuple[onp.Array1D[np.floating[Any]], ...] | None = None, skip_tests: bool = False) -> Mesh: """Constructs a grid of copies of *mesh*, with *shape* copies in each dimensions at the given *offset*. @@ -1560,7 +1566,9 @@ def remove_unused_vertices(mesh: Mesh) -> Mesh: if mesh.vertices is None: raise ValueError("mesh must have vertices") - def not_none(vi: NDArray[np.integer] | None) -> NDArray[np.integer]: + def not_none( + vi: onp.Array2D[np.integer[Any]] | None + ) -> onp.Array2D[np.integer[Any]]: if vi is None: raise ValueError("mesh element groups must have vertex indices") return vi diff --git a/meshmode/mesh/refinement/tessellate.py b/meshmode/mesh/refinement/tessellate.py index 3ff5b13f..e9e1bf34 100644 --- a/meshmode/mesh/refinement/tessellate.py +++ b/meshmode/mesh/refinement/tessellate.py @@ -161,7 +161,7 @@ def _midpoint_tuples(a, b): def midpoint(x, y): d, r = divmod(x + y, 2) if r: - raise ValueError("%s is not evenly divisible by two" % x) + raise ValueError(f"'{x}' is not evenly divisible by two") return d diff --git a/meshmode/mesh/refinement/utils.py b/meshmode/mesh/refinement/utils.py index 5f29824e..9cdf7cb7 100644 --- a/meshmode/mesh/refinement/utils.py +++ b/meshmode/mesh/refinement/utils.py @@ -137,8 +137,7 @@ def is_symmetric(relation, debug=False): for b in other_list: if a not in relation[b]: if debug: - print("Relation is not symmetric: %s -> %s, but not %s -> %s" - % (a, b, b, a)) + print(f"Relation is not symmetric: {a} -> {b}, but not {b} -> {a}") return False return True diff --git a/meshmode/mesh/tools.py b/meshmode/mesh/tools.py index 5aad221f..911f79ab 100644 --- a/meshmode/mesh/tools.py +++ b/meshmode/mesh/tools.py @@ -94,7 +94,7 @@ def rand_rotation_matrix(ambient_dim, deflection=1.0, randnums=None, rng=None): # from https://blog.lostinmyterminal.com/python/2015/05/12/random-rotation-matrix.html # noqa: E501 if ambient_dim != 3: - raise NotImplementedError("ambient_dim=%d" % ambient_dim) + raise NotImplementedError(f"ambient_dim: {ambient_dim}") if randnums is None: if rng is None: diff --git a/meshmode/mesh/visualization.py b/meshmode/mesh/visualization.py index 98c990ad..b34691c8 100644 --- a/meshmode/mesh/visualization.py +++ b/meshmode/mesh/visualization.py @@ -102,7 +102,7 @@ def draw_2d_mesh( if len(mesh.groups) == 1: el_label = str(iel) else: - el_label = "%d:%d" % (igrp, iel) + el_label = f"{igrp}:{iel}" pt.text(centroid[0], centroid[1], el_label, fontsize=17, ha="center", va="center", @@ -207,7 +207,7 @@ def draw_curve( for i, group in enumerate(mesh.groups): plt.plot( group.nodes[0].T, - group.nodes[1].T, node_style, label="Group %d" % i, + group.nodes[1].T, node_style, label=f"Group {i}", **node_kwargs) # }}} @@ -312,8 +312,8 @@ def write_vertex_vtk_file( def mesh_to_tikz(mesh: Mesh) -> str: lines = [] - lines.append(r"\def\nelements{%s}" % mesh.nelements) - lines.append(r"\def\nvertices{%s}" % mesh.nvertices) + lines.append(fr"\def\nelements{{{mesh.nelements}}}") + lines.append(fr"\def\nvertices{{{mesh.nvertices}}}") lines.append("") drawel_lines = [] @@ -325,17 +325,17 @@ def mesh_to_tikz(mesh: Mesh) -> str: elverts = mesh.vertices[:, el] centroid = np.average(elverts, axis=1) - lines.append(r"\coordinate (cent%d) at (%s);" - % (el_nr, ", ".join(f"{vi:.5f}" for vi in centroid))) + coords = ", ".join(f"{vi:.5f}" for vi in centroid) + lines.append(fr"\coordinate (cent{el_nr}) at ({coords});") for ivert, vert in enumerate(elverts.T): - lines.append(r"\coordinate (v%d-%d) at (%s);" - % (el_nr, ivert+1, ", ".join(f"{vi:.5f}" for vi in vert))) - drawel_lines.append( - r"\draw [#1] %s -- cycle;" - % " -- ".join( - "(v%d-%d)" % (el_nr, vi+1) - for vi in range(elverts.shape[1]))) + coords = ", ".join(f"{vi:.5f}" for vi in vert) + lines.append(fr"\coordinate (v{el_nr}-{ivert + 1}) at ({coords});") + + cycle = " -- ".join(f"(v{el_nr}-{vi + 1})" + for vi in range(elverts.shape[1])) + drawel_lines.append(fr"\draw [#1] {cycle} -- cycle;") + drawel_lines.append("}") lines.append("") diff --git a/pyproject.toml b/pyproject.toml index ffec10a6..ba4320dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,9 +83,9 @@ extend-select = [ "NPY", # numpy "Q", # flake8-quotes "RUF", # ruff + "TC", # flake8-type-checking "UP", # pyupgrade "W", # pycodestyle - "TC", ] extend-ignore = [ "C90", # McCabe complexity @@ -98,7 +98,6 @@ extend-ignore = [ "N803", # argument name should be lowercase "N806", # variable name should be lowercase "N818", # error suffix in exception names - "UP031", # use f-strings instead of % format "UP032", # use f-strings instead of .format "RUF067", # no code in __init__ *shrug* ] diff --git a/test/test_mesh.py b/test/test_mesh.py index 30c522c5..cf6bc484 100644 --- a/test/test_mesh.py +++ b/test/test_mesh.py @@ -975,12 +975,12 @@ def test_boundary_tags(): # raise errors if wrong number of elements marked if num_marked_inner_bdy != num_on_inner_bdy: - raise ValueError("%i marked on inner boundary, should be %i" % - (num_marked_inner_bdy, num_on_inner_bdy)) + raise ValueError(f"%{num_marked_inner_bdy} marked on inner boundary, " + f"should be {num_on_inner_bdy}") if num_marked_outer_bdy != num_on_outer_bdy: - raise ValueError("%i marked on outer boundary, should be %i" % - (num_marked_outer_bdy, num_on_outer_bdy)) + raise ValueError(f"{num_marked_outer_bdy} marked on outer boundary, " + f"should be {num_on_outer_bdy}") # ensure boundary is covered from meshmode.mesh import check_bc_coverage @@ -1098,11 +1098,12 @@ def test_box_boundary_tags(dim, nelem, mesh_type, group_cls, visualize=False): # raise errors if wrong number of elements marked if num_marked_bdy_1 != num_on_bdy: - raise ValueError("%i marked on custom boundary 1, should be %i" % - (num_marked_bdy_1, num_on_bdy)) + raise ValueError(f"{num_marked_bdy_1} marked on custom boundary 1, " + f"should be {num_on_bdy}") + if num_marked_bdy_2 != num_on_bdy: - raise ValueError("%i marked on custom boundary 2, should be %i" % - (num_marked_bdy_2, num_on_bdy)) + raise ValueError(f"{num_marked_bdy_2} marked on custom boundary 2, " + f"should be {num_on_bdy}") # }}} diff --git a/test/test_meshmode.py b/test/test_meshmode.py index b744663d..d741c7bd 100644 --- a/test/test_meshmode.py +++ b/test/test_meshmode.py @@ -187,8 +187,7 @@ def f(x): # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) - print("h=%s -> %d elements" % ( - h, sum(mgrp.nelements for mgrp in mesh.groups))) + print(f"h={h} -> {mesh.nelements} elements") x = actx.thaw(vol_discr.nodes()[0]) vol_f = f(x) @@ -300,8 +299,7 @@ def f(x): # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) - print("h=%s -> %d elements" % ( - h, sum(mgrp.nelements for mgrp in mesh.groups))) + print(f"h={h} -> {mesh.nelements} elements") all_face_bdry_connection = make_face_restriction( actx, vol_discr, group_factory(order), @@ -453,8 +451,7 @@ def f(x): # }}} vol_discr = Discretization(actx, mesh, group_factory(order)) - print("h=%s -> %d elements" % ( - h, sum(mgrp.nelements for mgrp in mesh.groups))) + print(f"h={h} -> {mesh.nelements} elements") bdry_connection = make_face_restriction( actx, vol_discr, group_factory(order), diff --git a/test/test_partition.py b/test/test_partition.py index fd9d6ab6..9ca7a5ee 100644 --- a/test/test_partition.py +++ b/test/test_partition.py @@ -612,7 +612,7 @@ def test_mpi_communication(num_parts, order): "--oversubscribe", "-np", str(num_ranks), "-x", "RUN_WITHIN_MPI=1", - "-x", "order=%d" % order, + "-x", f"order={order}", # https://mpi4py.readthedocs.io/en/stable/mpi4py.run.html sys.executable, "-m", "mpi4py.run", __file__], diff --git a/test/test_refinement.py b/test/test_refinement.py index e7b83d22..7627e91f 100644 --- a/test/test_refinement.py +++ b/test/test_refinement.py @@ -235,7 +235,7 @@ def f(x): fine_vis = make_visualizer(actx, fine_discr, mesh_order) fine_vis.write_vtk_file( - "refine-fine-%s-%dd-%s.vtu" % (mesh_name, dim, mesh_par), [ + f"refine-fine-{mesh_name}-{dim}d-{mesh_par}.vtu", [ ("f_interp", f_interp), ("f_true", f_true), ])