Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial mypy support #356

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
24a6297
Add ignore all mypy config to pyproject.toml
schnellerhase Mar 2, 2025
6d58936
Add py.typed
schnellerhase Mar 2, 2025
367897c
Add mypy check to CI
schnellerhase Mar 2, 2025
aba7a3d
Add mypy to CI dependency
schnellerhase Mar 2, 2025
6ac4a31
Fix mypy error code 'index'
schnellerhase Mar 2, 2025
6993281
Fix mypy error code 'name-defined'
schnellerhase Mar 2, 2025
f887c42
Fix mypy error code 'return-value'
schnellerhase Mar 2, 2025
95c76d5
Fix mypy error code 'has-type'
schnellerhase Mar 2, 2025
35c2d5f
Add types-colorama to typing dependecies
schnellerhase Mar 2, 2025
8d69bd1
Fix mypy error code 'import-untyped'
schnellerhase Mar 2, 2025
ddccf07
Change base class of TensorProductCell to Cell
schnellerhase Mar 2, 2025
28e3e6b
Fix mypy error code 'override'
schnellerhase Mar 2, 2025
7f777aa
Switch to tpying.Tuple
schnellerhase Mar 2, 2025
a036787
Removed unused code
schnellerhase Mar 2, 2025
9ff27d7
Fix mypy error code 'var-annotated'
schnellerhase Mar 2, 2025
e5c95af
Replace typing.Self with older python variant
schnellerhase Mar 2, 2025
9f25a0f
Fix mypy error code 'method-assign'
schnellerhase Mar 2, 2025
c64d047
Fix mypy error code 'import-not-found'
schnellerhase Mar 2, 2025
cdbb08a
Ruff
schnellerhase Mar 2, 2025
4687473
Merge branch 'main' into mypy
schnellerhase Mar 6, 2025
57c69ce
Merge branch 'main' into mypy
jorgensd Mar 7, 2025
ee07111
Merge branch 'main' into mypy
jorgensd Mar 7, 2025
43c292d
Merge branch 'main' into mypy
garth-wells Mar 15, 2025
c559532
Merge branch 'main' into mypy
schnellerhase Mar 21, 2025
ec874db
Merge branch 'main' into mypy
mscroggs Mar 25, 2025
ed91dfc
Merge branch 'main' into mypy
mscroggs Mar 25, 2025
977a466
Remove unused _object_cache
schnellerhase Mar 25, 2025
14ce09a
Switch to make reconstruct return AbstractCell
schnellerhase Mar 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
ruff format --check .
- name: Install UFL
run: python -m pip install .[ci]
- name: Lint with mypy
run: mypy -p ufl
- name: Run unit tests
run: |
python -m pytest -n auto --cov=ufl/ --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml test/
Expand Down
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ issues = "https://github.com/FEniCS/ufl/issues"
funding = "https://numfocus.org/donate"

[project.optional-dependencies]
lint = ["ruff"]
lint = ["mypy", "ruff"]
typing = ["types-colorama"]
docs = ["sphinx", "sphinx_rtd_theme"]
test = ["pytest"]
ci = [
Expand All @@ -35,6 +36,7 @@ ci = [
"fenics-ufl[docs]",
"fenics-ufl[lint]",
"fenics-ufl[test]",
"fenics-ufl[typing]",
]

[tool.setuptools]
Expand Down Expand Up @@ -82,3 +84,13 @@ allowed-confusables = ["𝐚", "𝐀", "∕", "γ", "⨯", "∨"]

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.mypy]
disable_error_code = [
"arg-type",
"assignment",
"attr-defined",
"misc",
"operator",
"type-var",
]
3 changes: 2 additions & 1 deletion ufl/algorithms/formsplitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#
# Modified by Cecile Daversin-Catty, 2018

import typing
from typing import Optional

import numpy as np
Expand Down Expand Up @@ -135,7 +136,7 @@ def extract_blocks(
num_sub_elements = arguments[0].ufl_element().num_sub_elements
forms = []
for pi in range(num_sub_elements):
form_i = []
form_i: typing.List[typing.Optional[object]] = []
for pj in range(num_sub_elements):
f = fs.split(form, pi, pj)
if f.empty():
Expand Down
4 changes: 3 additions & 1 deletion ufl/algorithms/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later

import hashlib
import typing

from ufl.algorithms.domain_analysis import canonicalize_metadata
from ufl.classes import (
Expand All @@ -20,6 +21,7 @@
Label,
MultiIndex,
)
from ufl.core.ufl_type import UFLObject
from ufl.corealg.traversal import traverse_unique_terminals, unique_post_traversal


Expand Down Expand Up @@ -94,7 +96,7 @@ def compute_terminal_hashdata(expressions, renumbering):

def compute_expression_hashdata(expression, terminal_hashdata) -> bytes:
"""Compute expression hashdata."""
cache = {}
cache: typing.Dict[UFLObject, bytes] = {}

for expr in unique_post_traversal(expression):
# Uniquely traverse tree and hash each node
Expand Down
3 changes: 2 additions & 1 deletion ufl/algorithms/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Modified by Anders Logg, 2009-2010

import inspect
import typing

from ufl.algorithms.map_integrands import map_integrands
from ufl.classes import Variable, all_ufl_classes
Expand All @@ -35,7 +36,7 @@ class Transformer(object):
transform expression trees from one representation to another.
"""

_handlers_cache = {}
_handlers_cache: typing.Dict[type, typing.Tuple[str, bool]] = {}

def __init__(self, variable_cache=None):
"""Initialise."""
Expand Down
18 changes: 9 additions & 9 deletions ufl/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def peak_types(self) -> typing.Tuple[AbstractCell, ...]:
return self.sub_entity_types(tdim - 3)


_sub_entity_celltypes = {
_sub_entity_celltypes: typing.Dict[str, list[tuple[str, ...]]] = {
"vertex": [("vertex",)],
"interval": [tuple("vertex" for i in range(2)), ("interval",)],
"triangle": [
Expand Down Expand Up @@ -340,7 +340,7 @@ def reconstruct(self, **kwargs: typing.Any) -> Cell:
return Cell(self._cellname)


class TensorProductCell(AbstractCell):
class TensorProductCell(Cell):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this change deliberate? I guess we should talk to our firedrake friends (@dham) if this is something they want.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, fixes

ufl/cell.py:441: error: Incompatible return value type (got "TensorProductCell", expected "Cell")  [return-value]

Not entirely sure if it is the right fix to it though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the return type of reconstruct should be AbstractCell rather than Cell? This this can go back to inheriting from AbstractCell?

"""Tensor product cell."""

__slots__ = ("_cells", "_tdim")
Expand All @@ -358,7 +358,7 @@ def __init__(self, *cells: Cell):
if not isinstance(self._tdim, numbers.Integral):
raise ValueError("Expecting integer topological_dimension.")

def sub_cells(self) -> typing.List[AbstractCell]:
def sub_cells(self) -> typing.Tuple[AbstractCell, ...]:
"""Return list of cell factors."""
return self._cells

Expand Down Expand Up @@ -398,21 +398,21 @@ def num_sub_entities(self, dim: int) -> int:
def sub_entities(self, dim: int) -> typing.Tuple[AbstractCell, ...]:
"""Get the sub-entities of the given dimension."""
if dim < 0 or dim > self._tdim:
return []
return ()
if dim == 0:
return [Cell("vertex") for i in range(self.num_sub_entities(0))]
return tuple(Cell("vertex") for i in range(self.num_sub_entities(0)))
if dim == self._tdim:
return [self]
return (self,)
raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.")

def sub_entity_types(self, dim: int) -> typing.Tuple[AbstractCell, ...]:
"""Get the unique sub-entity types of the given dimension."""
if dim < 0 or dim > self._tdim:
return []
return ()
if dim == 0:
return [Cell("vertex")]
return (Cell("vertex"),)
if dim == self._tdim:
return [self]
return (self,)
raise NotImplementedError(f"TensorProductCell.sub_entities({dim}) is not implemented.")

def _lt(self, other) -> bool:
Expand Down
5 changes: 3 additions & 2 deletions ufl/constantvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# Modified by Massimiliano Leoni, 2016.

import numbers
import typing
from math import atan2

import ufl
Expand Down Expand Up @@ -65,7 +66,7 @@ class Zero(ConstantValue):

__slots__ = ("ufl_free_indices", "ufl_index_dimensions", "ufl_shape")

_cache = {}
_cache: typing.Dict[typing.Tuple[int], "Zero"] = {}

def __getnewargs__(self):
"""Get new args."""
Expand Down Expand Up @@ -362,7 +363,7 @@ class IntValue(RealValue):

__slots__ = ()

_cache = {}
_cache: typing.Dict[int, "IntValue"] = {}

def __getnewargs__(self):
"""Get new args."""
Expand Down
7 changes: 4 additions & 3 deletions ufl/core/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
# Modified by Anders Logg, 2008
# Modified by Massimiliano Leoni, 2016

import typing
import warnings

from ufl.core.ufl_type import UFLType, update_ufl_type_attributes
from ufl.core.ufl_type import UFLObject, UFLType, update_ufl_type_attributes


class Expr(object, metaclass=UFLType):
Expand Down Expand Up @@ -204,10 +205,10 @@ def __init__(self):

# A global dict mapping language_operator_name to the type it
# produces
_ufl_language_operators_ = {}
_ufl_language_operators_: typing.Dict[str, object] = {}

# List of all terminal modifier types
_ufl_terminal_modifiers_ = []
_ufl_terminal_modifiers_: typing.List[UFLObject] = []

# --- Mechanism for profiling object creation and deletion ---

Expand Down
6 changes: 4 additions & 2 deletions ufl/core/multiindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#
# Modified by Massimiliano Leoni, 2016.

import typing

from ufl.core.terminal import Terminal
from ufl.core.ufl_type import ufl_type
from ufl.utils.counted import Counted
Expand All @@ -30,7 +32,7 @@ class FixedIndex(IndexBase):

__slots__ = ("_hash", "_value")

_cache = {}
_cache: typing.Dict[int, "FixedIndex"] = {}

def __getnewargs__(self):
"""Get new args."""
Expand Down Expand Up @@ -116,7 +118,7 @@ class MultiIndex(Terminal):

__slots__ = ("_indices",)

_cache = {}
_cache: typing.Dict[typing.Tuple[int], "MultiIndex"] = {}

def __getnewargs__(self):
"""Get new args."""
Expand Down
8 changes: 4 additions & 4 deletions ufl/core/ufl_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -416,18 +416,18 @@ class UFLType(type):
_ufl_handler_name_ = "ufl_type"

# A global array of all Expr and BaseForm subclasses, indexed by typecode
_ufl_all_classes_ = []
_ufl_all_classes_: typing.List[UFLObject] = []

# A global set of all handler names added
_ufl_all_handler_names_ = set()
_ufl_all_handler_names_: typing.Set[str] = set()

# A global array of the number of initialized objects for each
# typecode
_ufl_obj_init_counts_ = []
_ufl_obj_init_counts_: typing.List[int] = []

# A global array of the number of deleted objects for each
# typecode
_ufl_obj_del_counts_ = []
_ufl_obj_del_counts_: typing.List[int] = []

# Type trait: If the type is abstract. An abstract class cannot
# be instantiated and does not need all properties specified.
Expand Down
3 changes: 2 additions & 1 deletion ufl/corealg/multifunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#
# Modified by Massimiliano Leoni, 2016

import typing
from inspect import signature

from ufl.core.expr import Expr
Expand Down Expand Up @@ -46,7 +47,7 @@ class MultiFunction(object):
algorithm object. Of course Python's function call overhead still applies.
"""

_handlers_cache = {}
_handlers_cache: typing.Dict[type, typing.Tuple[typing.List[str], bool]] = {}

def __init__(self):
"""Initialise."""
Expand Down
7 changes: 0 additions & 7 deletions ufl/exprequals.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
"""Expr equals."""

from collections import defaultdict

hash_total = defaultdict(int)
hash_collisions = defaultdict(int)
hash_equals = defaultdict(int)
hash_notequals = defaultdict(int)


def expr_equals(self, other):
"""Checks whether the two expressions are represented the exact same way.
Expand Down
4 changes: 2 additions & 2 deletions ufl/finiteelement.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __hash__(self) -> int:
"""Return a hash."""

@_abc.abstractmethod
def __eq__(self, other: AbstractFiniteElement) -> bool:
def __eq__(self, other: object) -> bool:
"""Check if this element is equal to another element."""

@_abc.abstractproperty
Expand Down Expand Up @@ -112,7 +112,7 @@ def sub_elements(self) -> _typing.List:
of sub-elements.
"""

def __ne__(self, other: AbstractFiniteElement) -> bool:
def __ne__(self, other: object) -> bool:
"""Check if this element is different to another element."""
return not self.__eq__(other)

Expand Down
2 changes: 1 addition & 1 deletion ufl/functionspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def components(self) -> typing.Dict[typing.Tuple[int, ...], int]:
if len(self._ufl_element.sub_elements) == 0:
return {(): 0}

components = {}
components: dict[typing.Tuple[int, ...], int] = {}
offset = 0
c_offset = 0
for s in self.ufl_sub_spaces():
Expand Down
2 changes: 1 addition & 1 deletion ufl/mathfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def evaluate(self, x, mapping, component, index_values):
"""Evaluate."""
a = self.ufl_operands[1].evaluate(x, mapping, component, index_values)
try:
import scipy.special
import scipy.special # type: ignore
except ImportError:
raise ValueError(
"You must have scipy installed to evaluate bessel functions in python."
Expand Down
4 changes: 2 additions & 2 deletions ufl/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
globals()[measure_name] = Measure(integral_type)

# TODO: Firedrake hack, remove later
ds_tb = ds_b + ds_t # noqa: F821
ds_tb = ds_b + ds_t # type: ignore # noqa: F821

# Default measure dX including both uncut and cut cells
dX = dx + dC # noqa: F821
dX = dx + dC # type: ignore # noqa: F821

# Create objects for builtin known cell types
vertex = Cell("vertex")
Expand Down
2 changes: 1 addition & 1 deletion ufl/pullback.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ class SymmetricPullback(AbstractPullback):
"""Pull back for an element with symmetry."""

def __init__(
self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing.tuple[int, ...], int]
self, element: _AbstractFiniteElement, symmetry: typing.Dict[typing.Tuple[int, ...], int]
):
"""Initalise.

Expand Down
Empty file added ufl/py.typed
Empty file.