"""Data used by the kernel object."""
__copyright__ = "Copyright (C) 2012 Andreas Kloeckner"
__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.
"""
import sys
from sys import intern
import numpy as np # noqa
from pytools import ImmutableRecord
from pytools.tag import Taggable
from pytools.tag import UniqueTag as UniqueTagBase
from loopy.kernel.array import ArrayBase
from loopy.diagnostic import LoopyError
from loopy.kernel.instruction import ( # noqa
InstructionBase,
MemoryOrdering,
MemoryScope,
VarAtomicity,
AtomicInit,
AtomicUpdate,
MultiAssignmentBase,
Assignment,
ExpressionInstruction,
CallInstruction,
make_assignment,
CInstruction)
from warnings import warn
__doc__ = """
.. currentmodule:: loopy.kernel.data
.. autofunction:: filter_iname_tags_by_type
.. autoclass:: InameImplementationTag
.. autoclass:: ConcurrentTag
.. autoclass:: UniqueInameTag
.. autoclass:: AxisTag
.. autoclass:: LocalInameTag
.. autoclass:: GroupInameTag
.. autoclass:: VectorizeTag
.. autoclass:: UnrollTag
.. autoclass:: Iname
.. autoclass:: KernelArgument
"""
class auto: # noqa
"""A generic placeholder object for something that should be automatically
determined. See, for example, the *shape* or *strides* argument of
:class:`ArrayArg`.
"""
# {{{ iname tags
def filter_iname_tags_by_type(tags, tag_type, max_num=None, min_num=None):
"""Return a subset of *tags* that matches type *tag_type*. Raises exception
if the number of tags found were greater than *max_num* or less than
*min_num*.
:arg tags: An iterable of tags.
:arg tag_type: a subclass of :class:`loopy.kernel.data.InameImplementationTag`.
:arg max_num: the maximum number of tags expected to be found.
:arg min_num: the minimum number of tags expected to be found.
"""
result = {tag for tag in tags if isinstance(tag, tag_type)}
def strify_tag_type():
if isinstance(tag_type, tuple):
return ", ".join(t.__name__ for t in tag_type)
else:
return tag_type.__name__
if max_num is not None:
if len(result) > max_num:
raise LoopyError("cannot have more than {} tags "
"of type(s): {}".format(max_num, strify_tag_type()))
if min_num is not None:
if len(result) < min_num:
raise LoopyError("must have more than {} tags "
"of type(s): {}".format(max_num, strify_tag_type()))
return result
class InameImplementationTag(ImmutableRecord, UniqueTagBase):
__slots__ = []
def __hash__(self):
return hash(self.key)
def __lt__(self, other):
return self.__hash__() < other.__hash__()
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
return key_builder.rec(key_hash, self.key)
@property
def key(self):
"""Return a hashable, comparable value that is used to ensure
per-instruction uniqueness of this unique iname tag.
Also used for persistent hash construction.
"""
return type(self).__name__
class ConcurrentTag(InameImplementationTag):
pass
class HardwareConcurrentTag(ConcurrentTag):
pass
# deprecated aliases
ParallelTag = ConcurrentTag
HardwareParallelTag = HardwareConcurrentTag
class UniqueInameTag(InameImplementationTag):
pass
class AxisTag(UniqueInameTag):
__slots__ = ["axis"]
def __init__(self, axis):
ImmutableRecord.__init__(self,
axis=axis)
@property
def key(self):
return (type(self).__name__, self.axis)
def __str__(self):
return "%s.%d" % (
self.print_name, self.axis)
class GroupInameTag(HardwareConcurrentTag, AxisTag):
print_name = "g"
class LocalInameTagBase(HardwareConcurrentTag):
pass
class LocalInameTag(LocalInameTagBase, AxisTag):
print_name = "l"
class AutoLocalInameTagBase(LocalInameTagBase):
@property
def key(self):
return type(self).__name__
class AutoFitLocalInameTag(AutoLocalInameTagBase):
def __str__(self):
return "l.auto"
# {{{ ilp-like
class IlpBaseTag(ConcurrentTag):
pass
class UnrolledIlpTag(IlpBaseTag):
def __str__(self):
return "ilp.unr"
class LoopedIlpTag(IlpBaseTag):
def __str__(self):
return "ilp.seq"
# }}}
class VectorizeTag(UniqueInameTag, HardwareConcurrentTag):
def __str__(self):
return "vec"
class UnrollTag(InameImplementationTag):
def __str__(self):
return "unr"
class ForceSequentialTag(InameImplementationTag):
def __str__(self):
return "forceseq"
class InOrderSequentialSequentialTag(InameImplementationTag):
def __str__(self):
return "ord"
def parse_tag(tag):
from pytools.tag import Tag as TagBase
if tag is None:
return tag
if isinstance(tag, TagBase):
return tag
if not isinstance(tag, str):
raise ValueError("cannot parse tag: %s" % tag)
if tag == "for":
return None
elif tag == "ord":
return InOrderSequentialSequentialTag()
elif tag in ["unr"]:
return UnrollTag()
elif tag in ["vec"]:
return VectorizeTag()
elif tag in ["ilp", "ilp.unr"]:
return UnrolledIlpTag()
elif tag == "ilp.seq":
return LoopedIlpTag()
elif tag.startswith("g."):
return GroupInameTag(int(tag[2:]))
elif tag.startswith("l."):
axis = tag[2:]
if axis == "auto":
return AutoFitLocalInameTag()
else:
return LocalInameTag(int(axis))
else:
raise ValueError("cannot parse tag: %s" % tag)
# }}}
# {{{ memory address space
class AddressSpace:
"""Storage location of a variable.
.. attribute:: PRIVATE
.. attribute:: LOCAL
.. attribute:: GLOBAL
"""
# These must occur in ascending order of 'globality' so that
# max(scope) does the right thing.
PRIVATE = 0
LOCAL = 1
GLOBAL = 2
@classmethod
def stringify(cls, val):
if val == cls.PRIVATE:
return "private"
elif val == cls.LOCAL:
return "local"
elif val == cls.GLOBAL:
return "global"
else:
raise ValueError("unexpected value of AddressSpace")
class _deprecated_temp_var_scope_class_method: # noqa
def __init__(self, f):
self.f = f
def __get__(self, obj, klass):
warn("'temp_var_scope' is deprecated. Use 'AddressSpace'.",
DeprecationWarning, stacklevel=2)
return self.f()
class temp_var_scope: # noqa
"""Deprecated. Use :class:`loopy.AddressSpace` instead.
"""
@_deprecated_temp_var_scope_class_method
def PRIVATE(): # pylint:disable=no-method-argument
return AddressSpace.PRIVATE
@_deprecated_temp_var_scope_class_method
def LOCAL(): # pylint:disable=no-method-argument
return AddressSpace.LOCAL
@_deprecated_temp_var_scope_class_method
def GLOBAL(): # pylint:disable=no-method-argument
return AddressSpace.GLOBAL
@classmethod
def stringify(cls, val):
warn("'temp_var_scope' is deprecated. Use 'AddressSpace'.",
DeprecationWarning, stacklevel=2)
return AddressSpace.stringify(val)
# }}}
# {{{ arguments
class KernelArgument(ImmutableRecord):
"""Base class for all argument types"""
def __init__(self, **kwargs):
kwargs["name"] = intern(kwargs.pop("name"))
target = kwargs.pop("target", None)
dtype = kwargs.pop("dtype", None)
if "for_atomic" in kwargs:
for_atomic = kwargs["for_atomic"]
else:
for_atomic = False
from loopy.types import to_loopy_type
dtype = to_loopy_type(
dtype, allow_auto=True, allow_none=True, for_atomic=for_atomic,
target=target)
import loopy as lp
if dtype is lp.auto:
warn("Argument/temporary data type for '%s' should be None if "
"unspecified, not auto. This usage will be disallowed in 2018."
% kwargs["name"],
DeprecationWarning, stacklevel=2)
dtype = None
kwargs["dtype"] = dtype
kwargs["is_output"] = kwargs.pop("is_output", None)
kwargs["is_input"] = kwargs.pop("is_input", None)
ImmutableRecord.__init__(self, **kwargs)
class ArrayArg(ArrayBase, KernelArgument):
__doc__ = ArrayBase.__doc__ + (
"""
.. attribute:: address_space
An attribute of :class:`AddressSpace` defining the address
space in which the array resides.
.. attribute:: is_output
An instance of :class:`bool`. If set to *True*, the array is used to
return information to the caller. If set to *False*, the callee does not
write to the array during a call.
.. attribute:: is_input
An instance of :class:`bool`. If set to *True*, expected to be provided
by the caller. If *False*, the callee does not depend on the array
at kernel entry.
""")
allowed_extra_kwargs = [
"address_space",
"is_output",
"is_input",
"tags"]
def __init__(self, *args, **kwargs):
if "address_space" not in kwargs:
raise TypeError("'address_space' must be specified")
is_output_only = kwargs.pop("is_output_only", None)
if is_output_only is not None:
warn("'is_output_only' is deprecated. Use 'is_output', 'is_input'"
" instead.", DeprecationWarning, stacklevel=2)
kwargs["is_output"] = is_output_only
kwargs["is_input"] = not is_output_only
else:
kwargs["is_output"] = kwargs.pop("is_output", None)
kwargs["is_input"] = kwargs.pop("is_input", None)
super().__init__(*args, **kwargs)
min_target_axes = 0
max_target_axes = 1
def get_arg_decl(self, ast_builder, name_suffix, shape, dtype, is_written):
return ast_builder.get_array_arg_decl(self.name + name_suffix,
self.address_space, shape, dtype, is_written)
def __str__(self):
# dont mention the type name if shape is known
include_typename = self.shape in (None, auto)
aspace_str = AddressSpace.stringify(self.address_space)
return (
self.stringify(include_typename=include_typename)
+
" aspace: %s" % aspace_str)
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
super().update_persistent_hash(key_hash, key_builder)
key_builder.rec(key_hash, self.address_space)
key_builder.rec(key_hash, self.is_output)
key_builder.rec(key_hash, self.is_input)
# Making this a function prevents incorrect use in isinstance.
# Note: This is *not* deprecated, as it is super-common and
# incrementally more convenient to use than ArrayArg directly.
def GlobalArg(*args, **kwargs):
address_space = kwargs.pop("address_space", None)
if address_space is not None:
raise TypeError("may not pass 'address_space' to GlobalArg")
kwargs["address_space"] = AddressSpace.GLOBAL
return ArrayArg(*args, **kwargs)
class ConstantArg(ArrayBase, KernelArgument):
__doc__ = ArrayBase.__doc__
def __init__(self, *args, **kwargs):
if kwargs.pop("address_space", AddressSpace.GLOBAL) != AddressSpace.GLOBAL:
raise LoopyError("'address_space' for ConstantArg must be GLOBAL.")
super().__init__(*args, **kwargs)
# Constant Arg cannot be an output
is_output = False
is_input = True
address_space = AddressSpace.GLOBAL
min_target_axes = 0
max_target_axes = 1
def get_arg_decl(self, ast_builder, name_suffix, shape, dtype, is_written):
return ast_builder.get_constant_arg_decl(self.name + name_suffix, shape,
dtype, is_written)
class ImageArg(ArrayBase, KernelArgument):
__doc__ = ArrayBase.__doc__
def __init__(self, *args, **kwargs):
if kwargs.pop("address_space", AddressSpace.GLOBAL) != AddressSpace.GLOBAL:
raise LoopyError("'address_space' for ImageArg must be GLOBAL.")
super().__init__(*args, **kwargs)
min_target_axes = 1
max_target_axes = 3
# ImageArg cannot be an output (for now)
is_output = False
is_input = True
address_space = AddressSpace.GLOBAL
@property
def dimensions(self):
return len(self.dim_tags)
def get_arg_decl(self, ast_builder, name_suffix, shape, dtype, is_written):
return ast_builder.get_image_arg_decl(self.name + name_suffix, shape,
self.num_target_axes(), dtype, is_written)
"""
:attribute tags: A (possibly empty) frozenset of instances of
:class:`pytools.tag.Tag` intended for consumption by an
application.
..versionadded: 2020.2.2
"""
class ValueArg(KernelArgument, Taggable):
def __init__(self, name, dtype=None, approximately=1000, target=None,
is_output=False, is_input=True, tags=None):
"""
:arg tags: A an instance of or Iterable of instances of
:class:`pytools.tag.Tag` intended for consumption by an
application.
"""
KernelArgument.__init__(self, name=name,
dtype=dtype,
approximately=approximately,
target=target,
is_output=is_output,
is_input=is_input,
tags=tags)
def __str__(self):
import loopy as lp
assert self.dtype is not lp.auto
if self.dtype is None:
type_str = "<auto/runtime>"
else:
type_str = str(self.dtype)
return f"{self.name}: ValueArg, type: {type_str}"
def __repr__(self):
return "<%s>" % self.__str__()
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
key_builder.rec(key_hash, self.name)
key_builder.rec(key_hash, self.dtype)
def get_arg_decl(self, ast_builder):
return ast_builder.get_value_arg_decl(self.name, (),
self.dtype, False)
class InameArg(ValueArg):
pass
# }}}
# {{{ temporary variable
[docs]class TemporaryVariable(ArrayBase):
__doc__ = ArrayBase.__doc__ + """
.. attribute:: storage_shape
.. attribute:: base_indices
.. attribute:: address_space
What memory this temporary variable lives in.
One of the values in :class:`AddressSpace`,
or :class:`loopy.auto` if this is
to be automatically determined.
.. attribute:: base_storage
The name of a storage array that is to be used to actually
hold the data in this temporary. Note that this storage
array must not match any existing variable names.
.. attribute:: initializer
*None* or a :class:`numpy.ndarray` of data to be used to initialize the
array.
.. attribute:: read_only
A :class:`bool` indicating whether the variable may be written during
its lifetime. If *True*, *initializer* must be given.
.. attribute:: _base_storage_access_may_be_aliasing
Whether the temporary is used to alias the underlying base storage.
Defaults to *False*. If *False*, C-based code generators will declare
the temporary as a ``restrict`` const pointer to the base storage
memory location. If *True*, the restrict part is omitted on this
declaration.
"""
min_target_axes = 0
max_target_axes = 1
allowed_extra_kwargs = [
"storage_shape",
"base_indices",
"address_space",
"base_storage",
"initializer",
"read_only",
"_base_storage_access_may_be_aliasing",
]
def __init__(self, name, dtype=None, shape=auto, address_space=None,
dim_tags=None, offset=0, dim_names=None, strides=None, order=None,
base_indices=None, storage_shape=None,
base_storage=None, initializer=None, read_only=False,
_base_storage_access_may_be_aliasing=False, **kwargs):
"""
:arg dtype: :class:`loopy.auto` or a :class:`numpy.dtype`
:arg shape: :class:`loopy.auto` or a shape tuple
:arg base_indices: :class:`loopy.auto` or a tuple of base indices
"""
scope = kwargs.pop("scope", None)
if scope is not None:
warn("Passing 'scope' is deprecated. Use 'address_space' instead.",
DeprecationWarning, stacklevel=2)
if address_space is not None:
raise ValueError("only one of 'scope' and 'address_space' "
"may be specified")
else:
address_space = scope
del scope
if address_space is None:
address_space = auto
if address_space is None:
raise LoopyError(
"temporary variable '%s': "
"address_space must not be None"
% name)
if initializer is None:
pass
elif isinstance(initializer, np.ndarray):
if offset != 0:
raise LoopyError(
"temporary variable '%s': "
"offset must be 0 if initializer specified"
% name)
from loopy.types import NumpyType, to_loopy_type
if dtype is auto or dtype is None:
dtype = NumpyType(initializer.dtype)
elif to_loopy_type(dtype) != to_loopy_type(initializer.dtype):
raise LoopyError(
"temporary variable '%s': "
"dtype of initializer does not match "
"dtype of array."
% name)
if shape is auto:
shape = initializer.shape
else:
if shape != initializer.shape:
raise LoopyError("Shape of '{}' does not match that of the"
" initializer.".format(name))
else:
raise LoopyError(
"temporary variable '%s': "
"initializer must be None or a numpy array"
% name)
if order is None:
order = "C"
if base_indices is None and shape is not auto:
base_indices = (0,) * len(shape)
if not read_only and initializer is not None:
raise LoopyError(
"temporary variable '%s': "
"read-write variables with initializer "
"are not currently supported "
"(did you mean to set read_only=True?)"
% name)
if base_storage is not None and initializer is not None:
raise LoopyError(
"temporary variable '%s': "
"base_storage and initializer are "
"mutually exclusive"
% name)
if base_storage is None and _base_storage_access_may_be_aliasing:
raise LoopyError(
"temporary variable '%s': "
"_base_storage_access_may_be_aliasing option, but no "
"base_storage given!"
% name)
ArrayBase.__init__(self, name=intern(name),
dtype=dtype, shape=shape, strides=strides,
dim_tags=dim_tags, offset=offset, dim_names=dim_names,
order=order,
base_indices=base_indices,
address_space=address_space,
storage_shape=storage_shape,
base_storage=base_storage,
initializer=initializer,
read_only=read_only,
_base_storage_access_may_be_aliasing=(
_base_storage_access_may_be_aliasing),
**kwargs)
@property
def scope(self):
warn("Use of 'TemporaryVariable.scope' is deprecated, "
"use 'TemporaryVariable.address_space' instead.",
DeprecationWarning, stacklevel=2)
return self.address_space
def copy(self, **kwargs):
address_space = kwargs.pop("address_space", None)
scope = kwargs.pop("scope", None)
if scope is not None:
warn("Passing 'scope' is deprecated. Use 'address_space' instead.",
DeprecationWarning, stacklevel=2)
if address_space is not None:
raise ValueError("only one of 'scope' and 'address_space' "
"may be specified")
else:
address_space = scope
del scope
if address_space is not None:
kwargs["address_space"] = address_space
return super().copy(**kwargs)
@property
def nbytes(self):
shape = self.shape
if self.storage_shape is not None:
shape = self.storage_shape
from pytools import product
return product(si for si in shape)*self.dtype.itemsize
def decl_info(self, target, index_dtype):
return super().decl_info(
target, is_written=True, index_dtype=index_dtype,
shape_override=self.storage_shape)
def get_arg_decl(self, ast_builder, name_suffix, shape, dtype, is_written):
if self.address_space == AddressSpace.GLOBAL:
return ast_builder.get_array_arg_decl(self.name + name_suffix,
AddressSpace.GLOBAL, shape, dtype, is_written)
else:
raise LoopyError("unexpected request for argument declaration of "
"non-global temporary")
def __str__(self):
if self.address_space is auto:
scope_str = "auto"
else:
scope_str = AddressSpace.stringify(self.address_space)
return (
self.stringify(include_typename=False)
+
" scope:%s" % scope_str)
def __eq__(self, other):
return (
super().__eq__(other)
and self.storage_shape == other.storage_shape
and self.base_indices == other.base_indices
and self.address_space == other.address_space
and self.base_storage == other.base_storage
and (
(self.initializer is None and other.initializer is None)
or np.array_equal(self.initializer, other.initializer))
and self.read_only == other.read_only
and (self._base_storage_access_may_be_aliasing
== other._base_storage_access_may_be_aliasing)
)
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
super().update_persistent_hash(key_hash, key_builder)
self.update_persistent_hash_for_shape(key_hash, key_builder,
self.storage_shape)
key_builder.rec(key_hash, self.base_indices)
key_builder.rec(key_hash, self.address_space)
key_builder.rec(key_hash, self.base_storage)
initializer = self.initializer
if initializer is not None:
initializer = (initializer.tolist(), initializer.dtype)
key_builder.rec(key_hash, initializer)
key_builder.rec(key_hash, self.read_only)
key_builder.rec(key_hash, self._base_storage_access_may_be_aliasing)
# }}}
def iname_tag_to_temp_var_scope(iname_tag):
iname_tag = parse_tag(iname_tag)
if isinstance(iname_tag, GroupInameTag):
return AddressSpace.GLOBAL
elif isinstance(iname_tag, LocalInameTag):
return AddressSpace.LOCAL
else:
return AddressSpace.PRIVATE
# {{{ substitution rule
class SubstitutionRule(ImmutableRecord):
"""
.. attribute:: name
.. attribute:: arguments
A tuple of strings
.. attribute:: expression
"""
def __init__(self, name, arguments, expression):
assert isinstance(arguments, tuple)
ImmutableRecord.__init__(self,
name=name, arguments=arguments, expression=expression)
def __str__(self):
return "{}({}) := {}".format(
self.name, ", ".join(self.arguments), self.expression)
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
key_builder.rec(key_hash, self.name)
key_builder.rec(key_hash, self.arguments)
key_builder.update_for_pymbolic_expression(key_hash, self.expression)
# }}}
# {{{ function call mangling
class CallMangleInfo(ImmutableRecord):
"""
.. attribute:: target_name
A string. The name of the function to be called in the
generated target code.
.. attribute:: result_dtypes
A tuple of :class:`loopy.types.LoopyType` instances indicating what
types of values the function returns.
.. attribute:: arg_dtypes
A tuple of :class:`loopy.types.LoopyType` instances indicating what
types of arguments the function actually receives.
"""
def __init__(self, target_name, result_dtypes, arg_dtypes):
assert isinstance(result_dtypes, tuple)
super().__init__(
target_name=target_name,
result_dtypes=result_dtypes,
arg_dtypes=arg_dtypes)
# }}}
# {{{ Iname class
class Iname(Taggable):
"""
Records an iname in a :class:`~loopy.LoopKernel`. See :ref:`domain-tree` for
semantics of *inames* in :mod:`loopy`.
This class records the metadata attached to an iname as instances of
:class:pytools.tag.Tag`. A tag maybe a builtin tag like
:class:`loopy.kernel.data.InameImplementationTag` or a user-defined custom
tag. Custom tags may be attached to inames to be used in targeting later
during transformations.
.. attribute:: name
An instance of :class:`str`, denoting the iname's name.
.. attribute:: tas
An instance of :class:`frozenset` of :class:`pytools.tag.Tag`.
"""
def __init__(self, name, tags=frozenset()):
super().__init__(tags=tags)
assert isinstance(name, str)
self.name = name
def copy(self, *, name=None, tags=None):
if name is None:
name = self.name
if tags is None:
tags = self.tags
return type(self)(name=name, tags=tags)
def update_persistent_hash(self, key_hash, key_builder):
"""Custom hash computation function for use with
:class:`pytools.persistent_dict.PersistentDict`.
"""
key_builder.rec(key_hash, type(self).__name__.encode("utf-8"))
key_builder.rec(key_hash, self.name)
key_builder.rec(key_hash, self.tags)
def __eq__(self, other):
return (
type(self) == type(other)
and self.name == other.name
and self.tags == other.tags)
# }}}
# {{{ deprecation helpers
_old_to_new = {
"IndexTag": "InameImplementationTag",
"GroupIndexTag": "GroupInameTag",
"LocalIndexTagBase": "LocalInameTagBase",
"LocalIndexTag": "LocalInameTag",
"UniqueTag": "UniqueInameTag",
}
if sys.version_info < (3, 7):
_glb = globals()
for _old, _new in _old_to_new.items():
_glb[_old] = _glb[_new]
del _old
del _new
del _glb
else:
def __getattr__(name):
new_name = _old_to_new.get(name)
if new_name is None:
raise AttributeError(name)
else:
from warnings import warn
warn(f"loopy.kernel.data.{name} is deprecated. "
f"Use loopy.kernel.data.{new_name} instead. "
"The old name will stop working in 2022.",
DeprecationWarning, stacklevel=2)
return globals()[new_name]
# }}}
# vim: foldmethod=marker