diff --git a/doc/design.rst b/doc/design.rst index 7837eaf5032cf9287c0f44d139fc44d5b3f637a6..1feecc3dbee326e574105a145c7e04ab75c91573 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -1,8 +1,70 @@ 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 +- Results of computations are either implement the :class:`~Array` + interface or are a :class:`~DictOfNamedArrays`. + The former are referred to as an :term:`array expression`. The union type + of both of them is referred to as an *array result*. (FIXME? name) + +- There is one (for now) computation :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` outside + the reserved sub-namespace of identifiers beginning with + ``_pt`` are not generated automatically without explicit user requests. + +- :attr:`Array.shape` and :attr:`Array.dtype` are evaluated eagerly. + +- 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. + +- Results of array computations that are scalar (i.e. an :attr:`Array.shape` of `()`) + and have an integral :attr:`Array.dtype` (i.e. ``dtype.kind == "i"``) may be used in + shapes once they have been assigned a name. + + For some computations 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:: + + B = A[(A > 0).tagged(CountNamed("mycount"))] + +Glossary +-------- + +.. glossary:: + + array expression + An object implementing the :clas:`~Array` interface + + array result + An :class:`array expression` or an instance of + :class:`~DictOfNamedArrays`. + + identifier + Any string matching the regular expression + ``[a-zA-z_][a-zA-Z0-9_]+`` that does not + start with ``_pt``, ``_lp``, or a double underscore. + +Reserved Identifiers +-------------------- + +Identifiers beginning with ``_pt`` are reserved for internal use +by :module:`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. diff --git a/pytato/__init__.py b/pytato/__init__.py index 5f047b9d1238f8ddfed2a4ac5ffa2c6d8745487b..de2ea921b3668ea0c972f35e00796314756d0f22 100644 --- a/pytato/__init__.py +++ b/pytato/__init__.py @@ -25,7 +25,8 @@ THE SOFTWARE. """ from pytato.array import ( - DottedName, Namespace, Array, DictOfNamedArrays, + DottedName, Namespace, Array, DictOfNamedArrays, Tag ) -__all__ = ("DottedName", "Namespace", "Array", "DictOfNamedArrays") +__all__ = ("DottedName", "Namespace", "Array", "DictOfNamedArrays", + "Tag") diff --git a/pytato/array.py b/pytato/array.py index 918b53ebfc6261c68dd8a6a4f41ad68a560a41ce..de78540204bac53a0eeaeb97ee172801e809a318 100644 --- a/pytato/array.py +++ b/pytato/array.py @@ -24,21 +24,23 @@ 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:`DataWrapper`. Array Interface --------------- -.. currentmodule:: pytato - .. autoclass :: Namespace .. autoclass :: Array +.. autoclass :: Tag .. autoclass :: DictOfNamedArrays Supporting Functionality @@ -57,35 +59,54 @@ Built-in Expression Nodes .. autoclass:: LoopyFunction """ +# }}} + import collections.abc +from dataclasses import dataclass from pytools import single_valued, is_single_valued +# {{{ dotted name + class DottedName: """ .. attribute:: name_parts A tuple of strings, each of which is a valid - Python identifier. + 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. + + .. automethod:: from_class """ def __init__(self, name_parts): self.name_parts = name_parts + @classmethod + def from_class(cls, argcls): + name_parts = tuple(".".split(argcls.__module__) + [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) + +# }}} + + +# {{{ namespace class Namespace: # Possible future extension: .parent attribute """ .. attribute:: symbol_table - A mapping from strings that must be valid - C identifiers to objects implementing the - :class:`Array` interface. + A mapping from :term:`identifier` strings + to :term:`array expression`s. """ def __init__(self): @@ -96,6 +117,46 @@ class Namespace: raise ValueError(f"'{name}' is already assigned") self.symbol_table[name] = value +# }}} + + +# {{{ tag + +tag_dataclass = dataclass(init=True, eq=True, frozen=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. + """ + + @property + def tag_name(self): + 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. + """ + +# }}} + + +# {{{ array inteface class Array: """ @@ -138,8 +199,7 @@ class Array: .. 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 `__ @@ -160,6 +220,10 @@ class Array: This mirrors the tagging scheme that :mod:`loopy` is headed towards. + .. automethod:: named + .. automethod:: tagged + .. automethod:: without_tag + Derived attributes: .. attribute:: ndim @@ -192,32 +256,79 @@ class Array: def shape(self): raise NotImplementedError + def named(self, name): + self.namespace.assign_name(name, self) + return self.copy(name=name) + @property def ndim(self): return len(self.shape) - def with_tag(self, dotted_name, args=None): + def tagged(self, tag: Tag): """ - 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 + pass def without_tag(self, dotted_name): pass - def with_name(self, name): - self.namespace.assign_name(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 + +@tag_dataclass +class CountNamed(UniqueTag): + """ + .. attribute:: name + """ + + name: str + +# }}} + + +# {{{ dict of named arrays class DictOfNamedArrays(collections.abc.Mapping): """A container that maps valid Python identifiers @@ -260,6 +371,10 @@ class DictOfNamedArrays(collections.abc.Mapping): def __len__(self): return len(self._data) +# }}} + + +# {{{ index lambda class IndexLambda(Array): """ @@ -285,11 +400,19 @@ class IndexLambda(Array): :attr:`index_expr`. """ +# }}} + + +# {{{ einsum class Einsum(Array): """ """ +# }}} + + +# {{{ data wrapper class DataWrapper(Array): # TODO: Name? @@ -305,6 +428,10 @@ class DataWrapper(Array): """ +# }}} + + +# {{{ placeholder class Placeholder(Array): """ @@ -335,6 +462,10 @@ class Placeholder(Array): self._shape = shape +# }}} + + +# {{{ loopy function class LoopyFunction(DictOfNamedArrays): """ @@ -344,3 +475,7 @@ class LoopyFunction(DictOfNamedArrays): and one that's obtained by importing a dotted name. """ + +# }}} + +# vim: foldmethod=marker 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',