diff --git a/.gitignore b/.gitignore index 7a7df094eaf4bac78ca41b3bb411b0218aa71c4b..d97d175d3a129309a5a2be0dd04239c02fee31ed 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ distribute*tar.gz .cache .ipynb_checkpoints + +doc/_build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46cf8170af03dc88bdc28d5d25bb6e883ad21d8d..c0b6f9fa7d279dce86e478a8b0c4a36fbb0381d2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ Python 3 POCL: script: - export PY_EXE=python3 - - export PYOPENCL_TEST=portable + - export PYOPENCL_TEST=portable:pthread - export EXTRA_INSTALL="pyopencl" - curl -L -O -k https://gitlab.tiker.net/inducer/ci-support/raw/master/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" diff --git a/doc/design.rst b/doc/design.rst index 7837eaf5032cf9287c0f44d139fc44d5b3f637a6..3fdcb171b611ba28f2cbde420b508e8627985bee 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -1,8 +1,153 @@ Design Decisions in Pytato ========================== -- There is one (for now) computation :class:`pytato.N -- Shapes and dtypes are computed eagerly. -- Array data is computed eagerly. -- Results of array computations may *beomc +.. currentmodule:: pytato +TODO +---- + +- reduction inames +- finish trawling the design doc +- expression nodes in index lambda + - what pymbolic expression nodes are OK + - reductions + - function identifier scoping + - piecewise def (use ISL?) + +Computation and Results +----------------------- + +- Results of computations are either implement the :class:`~Array` + interface or are a :class:`~DictOfNamedArrays`. + The former are referred to as :term:`array expression`\ s. The union type + of both is referred to as an :term:`array result`. + +- Array data is computed lazily, i.e., a representation of the desired + computation is built, but computation/code generation is not carried + out until instructed by the user. Evaluation/computation + is never triggered implicitly. + +- :attr:`Array.dtype` is evaluated eagerly. + +- :attr:`Array.shape` is evaluated as eagerly as possible, however + data-dependent name references in shapes are allowed. (This implies + that the number of array axes must be statically known.) + + Consider the the example of fancy indexing:: + + A[A > 0] + + Here, the length of the resulting array depends on the data contained + in *A* and cannot be statically determined at code generation time. + + In the case of data-dependent shapes, the shape is expressed in terms of + scalar (i.e. an :attr:`Array.shape` of `()`) values + with an integral :attr:`Array.dtype` (i.e. ``dtype.kind == "i"``) + referenced by name from the :attr:`Array.namespace`. Such a name + marks the boundary between eager and lazy evaluation. + +- :class:`IndexLambda` is used to express the following functionality: + + - Broadcasting (equivalently, outer products) + - Slicing + - Arithmetic + - Element-wise function application, similar :class:`numpy.ufunc` + - Reductions + - Prefix sums/scans + + No new expression node types should be created for operations that + are well-expressed by :class:`IndexLambda`. An operation is well-expressed + if it is possible to (reasonably equivalently) recover the operation + (and its inputs and ordering with respect to other preceding/following + operations) by examining :attr:`IndexLambda.expr`. + + FIXME: This is not sharp. I'm not sure how to make it sharp. + +Naming +------ + +- There is one (for now) :class:`~Namespace` per computation that defines the + computational "environment". Operations involving array expressions not + using the same namespace are prohibited. + +- Names in the :class:`~Namespace` are under user control and unique. I.e. + new names in the :class:`~Namespace` that are not a + :ref:`reserved_identifier` are not generated automatically without explicit + user input. + +- The (array) value associated with a name is immutable once evaluated. + In-place slice assignment may be simulated by returning a new + node realizing a "partial replacement". + +- For arrays with data-dependent shapes, such as fancy indexing:: + + A[A > 0] + + it may be necessary to automatically generate names, in this + case to describe the shape of the index array used to realize + the access ``A[A>0]``. These will be drawn from the reserved namespace + ``_pt_shp``. Users may control the naming of these counts + by assigning the tag :attr:`pytato.array.CountNamed`, like so:: + + A[(A > 0).tagged(CountNamed("mycount"))] + +- :class:`Placeholder` expressions, like all array expressions, + are considered read-only. When computation begins, the same + actual memory may be supplied for multiple :term:`placeholder name`\ s, + i.e. those arrays may alias. + + .. note:: + + This does not preclude the arrays being declared with + C's ``*restrict`` qualifier in generated code, as they + do not alias any data that is being modified. + +.. _reserved_identifier: + +Reserved Identifiers +-------------------- + +- Identifiers beginning with ``_pt_`` are reserved for internal use + by :mod:`pytato`. Any such internal use must be drawn from one + of the following sub-regions, identified by their identifier + prefixes: + + - ``_pt_shp``: Used to automatically generate identifiers used + in data-dependent shapes. + +- Identifiers used in index lambdas are also reserved. These include: + + - Identifiers matching the regular expression ``_[0-9]+``. They are used + as index ("iname") placeholders. + + - Identifiers matching the regular expression ``_r[0-9]+``. They are used + as reduction indices. + + - Identifiers matching the regular expression ``_in[0-9]+``. They are used + as automatically generated names (if required) in + :attr:`IndexLambda.bindings`. + +Glossary +======== + +.. glossary:: + + array expression + An object implementing the :class:`~Array` interface + + array result + An :term:`array expression` or an instance of + :class:`~DictOfNamedArrays`. + + identifier + Any string for which :meth:`str.isidentifier` returns + *True*. See also :ref:`reserved_identifier`. + + namespace name + The name by which an :term:`array expression` is known + in a :class:`Namespace`. + + placeholder name + See :attr:`Placeholder.name`. + +.. vim: shiftwidth=4 diff --git a/pytato/__init__.py b/pytato/__init__.py index 5ca6a9ff007fa5b49996d8b5cde61ecc3011b72f..ad65a4cb19e70e418aece3ebca8c2164ee73a8ff 100644 --- a/pytato/__init__.py +++ b/pytato/__init__.py @@ -24,11 +24,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from pytato.typing import DottedName from pytato.array import ( - Namespace, Array, DictOfNamedArrays, - Placeholder, make_placeholder, + Namespace, Array, DictOfNamedArrays, Tag, + DottedName, Placeholder, make_placeholder, ) __all__ = ("DottedName", "Namespace", "Array", "DictOfNamedArrays", - "Placeholder", "make_placeholder") + "Tag", "Placeholder", "make_placeholder") diff --git a/pytato/array.py b/pytato/array.py index caf19bf4ca1651f7ea1edde6abebb33622302e84..e5d6818ef3e1df47afa2606c1900420d76e7a77d 100644 --- a/pytato/array.py +++ b/pytato/array.py @@ -26,92 +26,251 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -__doc__ = """ +# {{{ docs +__doc__ = """ +.. currentmodule:: pytato -Expression trees based on this package are picklable -as long as no non-picklable data -(e.g. :class:`pyopencl.array.Array`) -is referenced from :class:`DataWrapper`. +.. note:: + Expression trees based on this package are picklable + as long as no non-picklable data + (e.g. :class:`pyopencl.array.Array`) + is referenced from :class:`~pytato.array.DataWrapper`. Array Interface --------------- -.. currentmodule:: pytato - .. autoclass :: Namespace .. autoclass :: Array +.. autoclass :: Tag +.. autoclass :: UniqueTag .. autoclass :: DictOfNamedArrays +Supporting Functionality +------------------------ + +.. autoclass :: DottedName + +.. currentmodule:: pytato.array + +Pre-Defined Tags +---------------- + +.. autoclass:: ImplementAs +.. autoclass:: CountNamed + Built-in Expression Nodes ------------------------- -.. currentmodule:: pytato.array .. autoclass:: IndexLambda .. autoclass:: Einsum +.. autoclass:: Reshape .. autoclass:: DataWrapper .. autoclass:: Placeholder .. autoclass:: LoopyFunction User Interface for Making Nodes ------------------------------- -.. currentmodule:: pytato.array .. autofunction:: make_dict_of_named_arrays .. autofunction:: make_placeholder """ +# }}} + +import numpy as np +import pymbolic.primitives as prim -import pytato.typing as ptype +from dataclasses import dataclass from pytools import is_single_valued -from typing import Optional, Dict, Any, Mapping, Iterator +from typing import Optional, Dict, Any, Mapping, Iterator, Tuple, Union +# {{{ namespace -class Namespace(ptype.NamespaceInterface): - # Possible future extension: .parent attribute - """ - .. attribute:: symbol_table - A mapping from strings that must be valid - Python identifiers to objects implementing the - :class:`Array` interface. +class Namespace(): + r""" + Represents a mapping from :term:`identifier` strings to + :term:`array expression`\ s or *None*, where *None* indicates that the name + may not be used. (:class:`Placeholder` instances register their names in + this way to avoid ambiguity.) + + .. automethod:: __contains__ + .. automethod:: __getitem__ + .. automethod:: __iter__ + .. automethod:: assign + .. automethod:: ref """ def __init__(self) -> None: - self._symbol_table: Dict[str, ptype.ArrayInterface] = {} + self._symbol_table: Dict[str, Optional[Array]] = {} - def symbol_table(self) -> Dict[str, ptype.ArrayInterface]: - return self._symbol_table + def __contains__(self, name: str) -> bool: + return name in self._symbol_table + + def __getitem__(self, name: str) -> Array: + item = self._symbol_table[name] + if item is None: + raise ValueError("cannot access a reserved name") + return item + + def __iter__(self) -> Iterator[str]: + return iter(self._symbol_table) def assign(self, name: str, - value: ptype.ArrayInterface) -> None: + value: Optional[Array]) -> str: """Declare a new array. :param name: a Python identifier - :param value: the array object + :param value: the array object, or None if the assignment is to + just reserve a name + + :returns: *name* """ if name in self._symbol_table: raise ValueError(f"'{name}' is already assigned") self._symbol_table[name] = value + return name -class Array(ptype.ArrayInterface): + def ref(self, name: str) -> Array: + """ + :returns: An :term:`array expression` referring to *name*. + """ + + value = self[name] + + var_ref = prim.Variable(name) + if value.shape: + var_ref = var_ref[tuple("_%d" % i for i in range(len(value.shape)))] + + # FIXME: mypy thinks that this is Any + return IndexLambda( + self, expr=var_ref, shape=value.shape, + dtype=value.dtype) + +# }}} + +# {{{ tag + + +class DottedName(): """ - A base class (abstract interface + - supplemental functionality) for lazily - evaluating array expressions. + .. attribute:: name_parts - .. note:: + A tuple of strings, each of which is a valid + Python identifier. No name part may start with + a double underscore. + + The name (at least morally) exists in the + name space defined by the Python module system. + It need not necessarily identify an importable + object. - The interface seeks to maximize :mod:`numpy` - compatibility, though not at all costs. + .. automethod:: from_class + """ - All these are abstract: + def __init__(self, name_parts: Tuple[str, ...]): + self.name_parts = name_parts - .. attribute:: name + @classmethod + def from_class(cls, argcls: Any) -> DottedName: + name_parts = tuple( + [str(part) for part in argcls.__module__.split(".")] + + [str(argcls.__name__)]) + if not all(not npart.startswith("__") for npart in name_parts): + raise ValueError(f"some name parts of {'.'.join(name_parts)} " + "start with double underscores") + return cls(name_parts) + + +tag_dataclass = dataclass(init=True, eq=True, frozen=True, repr=True) + + +@tag_dataclass +class Tag: + """ + Generic metadata, applied to, among other things, + instances of :class:`Array`. + + .. attribute:: tag_name + + A fully qualified :class:`DottedName` that reflects + the class name of the tag. + + Instances of this type must be immutable, hashable, + picklable, and have a reasonably concise :meth:`__repr__` + of the form ``dotted.name(attr1=value1, attr2=value2)``. + Positional arguments are not allowed. - A name in :attr:`namespace` that has been assigned - to this expression. May be (and typically is) *None*. + .. note:: + + This mirrors the tagging scheme that :mod:`loopy` + is headed towards. + """ + + @property + def tag_name(self) -> DottedName: + return DottedName.from_class(type(self)) + + +class UniqueTag(Tag): + """ + Only one instance of this type of tag may be assigned + to a single tagged object. + """ + + +TagsType = Dict[DottedName, Tag] + +# }}} + +# {{{ shape + + +ShapeComponentType = Union[int, prim.Expression, str] +ShapeType = Tuple[ShapeComponentType, ...] + + +def check_shape(shape: ShapeType, + ns: Optional[Namespace] = None) -> bool: + """Checks for a shape tuple. + + :param shape: the shape tuple + + :param ns: if a namespace is given, extra checks are performed to + ensure that expressions are well-defined. + """ + for s in shape: + if isinstance(s, int): + assert s > 0, f"size parameter must be positive (got {s})" + elif isinstance(s, str): + assert str.isidentifier(s) + elif isinstance(s, prim.Expression) and ns is not None: + # TODO: check expression in namespace + pass + return True + +# }}} + +# {{{ array inteface + + +class Array: + """ + A base class (abstract interface + supplemental functionality) for lazily + evaluating array expressions. The interface seeks to maximize :mod:`numpy` + compatibility, though not at all costs. + + Objects of this type are hashable and support structural equality + comparison (and are therefore immutable). + + .. note:: + + Hashability and equality testing *does* break :mod:`numpy` + compatibility, purposefully so. + + FIXME: Point out our equivalent for :mod:`numpy`'s ``==``. .. attribute:: namespace @@ -121,13 +280,18 @@ class Array(ptype.ArrayInterface): .. attribute:: shape - Identifiers (:class:`pymbolic.Variable`) refer to - names from :attr:`namespace`. - A tuple of integers or :mod:`pymbolic` expressions. + Identifiers (:class:`pymbolic.Variable`) refer to names from + :attr:`namespace`. A tuple of integers or :mod:`pymbolic` expressions. Shape may be (at most affinely) symbolic in these identifiers. - # FIXME: -> https://gitlab.tiker.net/inducer/pytato/-/issues/1 + .. note:: + + Affine-ness is mainly required by code generation for + :class:`IndexLambda`, but :class:`IndexLambda` is used to produce + references to named arrays. Since any array that needs to be + referenced in this way needs to obey this restriction anyway, + a decision was made to requir the same of *all* array expressions. .. attribute:: dtype @@ -136,96 +300,118 @@ class Array(ptype.ArrayInterface): .. attribute:: tags A :class:`dict` mapping :class:`DottedName` instances - to an argument object, whose structure is defined - by the tag. + to instances of the :class:`Tag` interface. Motivation: `RDF `__ triples (subject: implicitly the array being tagged, predicate: the tag, object: the arg). - For example:: - - # tag - DottedName("our_array_thing.impl_mode"): - - # argument - DottedName( - "our_array_thing.loopy_target.subst_rule") - - .. note:: - - This mirrors the tagging scheme that :mod:`loopy` - is headed towards. + .. automethod:: named + .. automethod:: tagged + .. automethod:: without_tag Derived attributes: .. attribute:: ndim - Objects of this type are hashable and support - structural equality comparison (and are therefore - immutable). - - .. note:: - - This *does* break :mod:`numpy` compatibility, - purposefully so. """ - def __init__(self, namespace: ptype.NamespaceInterface, - name: Optional[str], - tags: Optional[ptype.TagsType] = None): + def __init__(self, namespace: Namespace, + tags: Optional[TagsType] = None): if tags is None: tags = {} - if name is not None: - namespace.assign(name, self) - self._namespace = namespace - self.name = name self.tags = tags + self.dtype: np.dtype = np.float64 # FIXME def copy(self, **kwargs: Any) -> Array: raise NotImplementedError @property - def namespace(self) -> ptype.NamespaceInterface: + def namespace(self) -> Namespace: return self._namespace @property - def shape(self) -> ptype.ShapeType: + def shape(self) -> ShapeType: raise NotImplementedError + def named(self, name: str) -> Array: + return self.namespace.ref(self.namespace.assign(name, self)) + @property def ndim(self) -> int: return len(self.shape) - def with_tag(self, dotted_name: ptype.DottedName, - args: Optional[ptype.DottedName] = None) -> Array: + def tagged(self, tag: Tag) -> Array: """ - Returns a copy of *self* tagged with *dotted_name* - and arguments *args* - If a tag *dotted_name* is already present, it is - replaced in the returned object. + Returns a copy of *self* tagged with *tag*. + If *tag* is a :class:`UniqueTag` and other + tags of this type are already present, an error + is raised. """ - if args is None: - pass + raise NotImplementedError return self.copy() - def without_tag(self, dotted_name: ptype.DottedName) -> Array: + def without_tag(self, dotted_name: DottedName) -> Array: raise NotImplementedError - def with_name(self, name: str) -> Array: - self.namespace.assign(name, self) - return self.copy(name=name) - # TODO: - # - tags # - codegen interface - # - naming +# }}} + +# {{{ pre-defined tag: ImplementAs + + +@tag_dataclass +class ImplementationStrategy(Tag): + pass + + +@tag_dataclass +class ImplStored(ImplementationStrategy): + pass + + +@tag_dataclass +class ImplInlined(ImplementationStrategy): + pass + + +@tag_dataclass +class ImplDefault(ImplementationStrategy): + pass + + +@tag_dataclass +class ImplementAs(UniqueTag): + """ + .. attribute:: strategy + """ + + strategy: ImplementationStrategy + +# }}} + +# {{{ pre-defined tag: CountNamed -class DictOfNamedArrays(Mapping[str, ptype.ArrayInterface]): + +@tag_dataclass +class CountNamed(UniqueTag): + """ + .. attribute:: name + """ + + name: str + +# }}} + +# {{{ dict of named arrays + + +class DictOfNamedArrays(Mapping[str, Array]): """A container that maps valid Python identifiers to instances of :class:`Array`. May occur as a result type of array computations. @@ -241,17 +427,17 @@ class DictOfNamedArrays(Mapping[str, ptype.ArrayInterface]): arithmetic. """ - def __init__(self, data: Dict[str, ptype.ArrayInterface]): + def __init__(self, data: Dict[str, Array]): self._data = data @property - def namespace(self) -> ptype.NamespaceInterface: + def namespace(self) -> Namespace: return next(iter(self._data.values())).namespace def __contains__(self, name: object) -> bool: return name in self._data - def __getitem__(self, name: str) -> ptype.ArrayInterface: + def __getitem__(self, name: str) -> Array: return self._data[name] def __iter__(self) -> Iterator[str]: @@ -260,36 +446,115 @@ class DictOfNamedArrays(Mapping[str, ptype.ArrayInterface]): def __len__(self) -> int: return len(self._data) +# }}} + +# {{{ index lambda + class IndexLambda(Array): """ - .. attribute:: index_expr + .. attribute:: expr A scalar-valued :mod:`pymbolic` expression such as - ``a[_1] + b[_2, _1]`` depending on TODO + ``a[_1] + b[_2, _1]``. Identifiers in the expression are resolved, in - order, by lookups in :attr:`inputs`, then in + order, by lookups in :attr:`bindings`, then in :attr:`namespace`. Scalar functions in this expression must - be identified by a dotted name - (e.g. ``our_array_thing.c99.sin``). + be identified by a dotted name representing + a Python object (e.g. ``pytato.c99.sin``). - .. attribute:: binding + .. attribute:: bindings A :class:`dict` mapping strings that are valid Python identifiers to objects implementing the :class:`Array` interface, making array expressions available for use in - :attr:`index_expr`. + :attr:`expr`. + + .. automethod:: is_reference """ + # TODO: write make_index_lambda() that does dtype inference + + def __init__( + self, namespace: Namespace, expr: prim.Expression, + shape: ShapeType, dtype: np.dtype, + bindings: Optional[Dict[str, Array]] = None, + tags: Optional[Dict[DottedName, Tag]] = None): + + if bindings is None: + bindings = {} + + super().__init__(namespace, tags=tags) + + self._shape = shape + self._dtype = dtype + self.expr = expr + self.bindings = bindings + + @property + def shape(self) -> ShapeType: + return self._shape + + @property + def dtype(self) -> np.dtype: + return self._dtype + + def is_reference(self) -> bool: + # FIXME: Do we want a specific 'reference' node to make all this + # checking unnecessary? + + if isinstance(self.expr, prim.Subscript): + assert isinstance(self.expr.aggregate, prim.Variable) + name = self.expr.aggregate.name + index = self.expr.index + elif isinstance(self.expr, prim.Variable): + name = self.expr.aggregate.name + index = () + else: + return False + + if index != tuple("_%d" % i for i in range(len(self.shape))): + return False + + try: + val = self.namespace[name] + except KeyError: + assert name in self.bindings + return False + + if self.shape != val.shape: + return False + if self.dtype != val.dtype: + return False + + return True + +# }}} + +# {{{ einsum + class Einsum(Array): """ """ +# }}} + +# {{{ reshape + + +class Reshape(Array): + """ + """ + +# }}} + +# {{{ data wrapper + class DataWrapper(Array): # TODO: Name? @@ -297,43 +562,77 @@ class DataWrapper(Array): Takes concrete array data and packages it to be compatible with the :class:`Array` interface. - A way - .. attrib + .. attribute:: data A concrete array (containing data), given as, for example, a :class:`numpy.ndarray`, or a :class:`pyopencl.array.Array`. + This must offer ``shape`` and ``dtype`` attributes but is + otherwise considered opaque. At evaluation time, its + type must be understood by the appropriate execution backend. + Starting with the construction of the :class:`DataWrapper`, + this array may not be updated in-place. """ + # TODO: not really Any data + def __init__(self, namespace: Namespace, data: Any, + tags: Optional[TagsType] = None): + super().__init__(namespace, tags) + + self.data = data + + @property + def shape(self) -> Any: # FIXME + return self.data.shape + + @property + def dtype(self) -> np.dtype: + return self.data.dtype + +# }}} + +# {{{ placeholder + class Placeholder(Array): """ A named placeholder for an array whose concrete value is supplied by the user during evaluation. + .. attribute:: name + + The name by which a value is supplied + for the placeholder once computation begins. + .. note:: - A symbolically represented - A symbolic - On - is required, and :attr:`shape` is given as data. + :attr:`name` is not a :term:`namespace name`. In fact, + it is prohibited from being one. (This has to be the case: Suppose a + :class:`Placeholder` is :meth:`~Array.tagged`, would the namespace name + refer to the tagged or the untagged version?) """ + def __init__(self, namespace: Namespace, + name: str, shape: ShapeType, + tags: Optional[TagsType] = None): + + # Reserve the name, prevent others from using it. + namespace.assign(name, None) + + super().__init__(namespace=namespace, tags=tags) + + self.name = name + self._shape = shape + @property - def shape(self) -> ptype.ShapeType: + def shape(self) -> ShapeType: # Matt added this to make Pylint happy. # Not tied to this, open for discussion about how to implement this. return self._shape - def __init__(self, namespace: ptype.NamespaceInterface, - name: str, shape: ptype.ShapeType, - tags: Optional[ptype.TagsType] = None): - super().__init__( - namespace=namespace, - name=name, - tags=tags) +# }}} - self._shape = shape +# {{{ loopy function class LoopyFunction(DictOfNamedArrays): @@ -345,12 +644,19 @@ class LoopyFunction(DictOfNamedArrays): name. """ +# }}} # {{{ end-user-facing +def make_dotted_name(name_parts: Tuple[str, ...]) -> DottedName: + assert len(name_parts) > 0 + assert all(str.isidentifier(p) for p in name_parts) + return DottedName(name_parts) + + def make_dict_of_named_arrays( - data: Dict[str, ptype.ArrayInterface]) -> DictOfNamedArrays: + data: Dict[str, Array]) -> DictOfNamedArrays: """Make a :class:`DictOfNamedArrays` object and ensure that all arrays share the same namespace. @@ -361,10 +667,10 @@ def make_dict_of_named_arrays( return DictOfNamedArrays(data) -def make_placeholder(namespace: ptype.NamespaceInterface, +def make_placeholder(namespace: Namespace, name: str, - shape: ptype.ShapeType, - tags: Optional[ptype.TagsType] = None + shape: ShapeType, + tags: Optional[TagsType] = None ) -> Placeholder: """Make a :class:`Placeholder` object. @@ -374,7 +680,9 @@ def make_placeholder(namespace: ptype.NamespaceInterface, :param tags: implementation tags """ assert str.isidentifier(name) - assert ptype.check_shape(shape, namespace) + assert check_shape(shape, namespace) return Placeholder(namespace, name, shape, tags) # }}} End end-user-facing + +# vim: foldmethod=marker diff --git a/pytato/typing.py b/pytato/typing.py deleted file mode 100644 index 7383a872e328f0bbc078b91d4b278030cc99ef80..0000000000000000000000000000000000000000 --- a/pytato/typing.py +++ /dev/null @@ -1,162 +0,0 @@ -from __future__ import annotations - -__copyright__ = """ -Copyright (C) 2020 Andreas Kloeckner -Copyright (C) 2020 Matt Wala -Copyright (C) 2020 Xiaoyu Wei -""" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -__doc__ = """ - - -Interface classes and type specifications. -Some types are paired with ``check_*`` functions that, -when used together, achieves contracts-like functionality. - -Abstract Classes ----------------- - -.. autoclass :: NamespaceInterface - -.. autoclass :: ArrayInterface - -Supporting Functionalities --------------------------- - -.. autoclass :: DottedName - -.. autofunction :: check_shape - -""" - -from pymbolic.primitives import Expression -from abc import ABC, abstractmethod -from typing import Optional, Union, Dict, Tuple, Any, List - -# {{{ abstract classes - - -class NamespaceInterface(ABC): - - @property - @abstractmethod - def symbol_table(self) -> Dict[str, ArrayInterface]: - pass - - @abstractmethod - def assign(self, name: str, - value: ArrayInterface) -> None: - pass - - -class ArrayInterface(ABC): - """Abstract class for types implementing the Array interface. - """ - - @property - @abstractmethod - def namespace(self) -> NamespaceInterface: - pass - - @property - @abstractmethod - def ndim(self) -> Any: - pass - - @property - @abstractmethod - def shape(self) -> Any: - pass - - @abstractmethod - def copy(self, **kwargs: Any) -> ArrayInterface: - pass - - @abstractmethod - def with_tag(self, tag_key: Any, - tag_val: Any) -> ArrayInterface: - pass - - @abstractmethod - def without_tag(self, tag_key: Any) -> ArrayInterface: - pass - - @abstractmethod - def with_name(self, name: str) -> ArrayInterface: - pass - -# }}} End abstract classes - -# {{{ shape type - - -ShapeComponentType = Union[int, Expression, str] -ShapeType = Tuple[ShapeComponentType, ...] - - -def check_shape(shape: ShapeType, - ns: Optional[NamespaceInterface] = None) -> bool: - """Checks for a shape tuple. - - :param shape: the shape tuple - - :param ns: if a namespace is given, extra checks are performed to - ensure that expressions are well-defined. - """ - for s in shape: - if isinstance(s, int): - assert s > 0, f"size parameter must be positive (got {s})" - elif isinstance(s, str): - assert str.isidentifier(s) - elif isinstance(s, Expression) and ns is not None: - # TODO: check expression in namespace - pass - return True - -# }}} End shape type - -# {{{ tags type - - -class DottedName(): - """ - .. attribute:: name_parts - - A tuple of strings, each of which is a valid - Python identifier (non-Unicode Python identifier). - - The name (at least morally) exists in the - name space defined by the Python module system. - It need not necessarily identify an importable - object. - """ - - def __init__(self, name_parts: List[str]): - assert len(name_parts) > 0 - assert all(str.isidentifier(p) for p in name_parts) - self.name_parts = name_parts - - -TagsType = Dict[DottedName, DottedName] - -# }}} End tags type diff --git a/setup.cfg b/setup.cfg index a8663c0804824e4362183ced8a8a576a5356e269..e52dab2055a6d24ad783572177a839334530598e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,8 +2,11 @@ ignore = E126,E127,E128,E123,E226,E241,E242,E265,N802,W503,E402,N814,N817,W504 max-line-length=85 -[mypy-pymbolic.primitives] +[mypy-pymbolic] ignore_missing_imports = True [mypy-pytools] ignore_missing_imports = True + +[mypy-numpy] +ignore_missing_imports = True diff --git a/setup.py b/setup.py index c7d57e46918732720ce87672cf4f78c0895a24f3..a0f3d056b619010d5b68cdb4178c4d63e2d7f524 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,6 @@ setup(name="pytato", 'Programming Language :: Python', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Scientific/Engineering',