class FieldStrideSymbol(TypedSymbol): """Sympy symbol representing the stride value of a field in a specific coordinate.""" def __new__(cls, *args, **kwds): obj = FieldStrideSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, field_name, coordinate): name = f"_stride_{field_name}_{coordinate}" obj = super(FieldStrideSymbol, cls).__xnew__(cls, name, STRIDE_DTYPE, positive=True) obj.field_name = field_name obj.coordinate = coordinate return obj def __getnewargs__(self): return self.field_name, self.coordinate def __getnewargs_ex__(self): return (self.field_name, self.coordinate), {} __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def _hashable_content(self): return super()._hashable_content(), self.coordinate, self.field_name
class Scalar(Symbol, ArgProvider): """ Like a Symbol, but in addition it can pass runtime values to an Operator. Parameters ---------- name : str Name of the symbol. dtype : data-type, optional Any object that can be interpreted as a numpy data type. Defaults to ``np.float32``. is_const : bool, optional True if the symbol value cannot be modified within an Operator, False otherwise. Defaults to False. """ is_Scalar = True def __new__(cls, name, dtype=np.float32, is_const=False): return Scalar.__xnew_cached_(cls, name, dtype, is_const) def __new_stage2__(cls, name, dtype, is_const): newobj = Symbol.__xnew__(cls, name, dtype) newobj._is_const = is_const return newobj @property def is_const(self): return self._is_const __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) # Pickling support _pickle_kwargs = Symbol._pickle_kwargs + ['is_const']
class FieldShapeSymbol(TypedSymbol): """Sympy symbol representing the shape value of a sequence of fields. In a kernel iterating over multiple fields there is only one set of `FieldShapeSymbol`s since all the fields have to be of equal size.""" def __new__(cls, *args, **kwds): obj = FieldShapeSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, field_names, coordinate): names = "_".join([field_name for field_name in field_names]) name = f"_size_{names}_{coordinate}" obj = super(FieldShapeSymbol, cls).__xnew__(cls, name, SHAPE_DTYPE, positive=True) obj.field_names = tuple(field_names) obj.coordinate = coordinate return obj def __getnewargs__(self): return self.field_names, self.coordinate def __getnewargs_ex__(self): return (self.field_names, self.coordinate), {} __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def _hashable_content(self): return super()._hashable_content(), self.coordinate, self.field_names
class ModuloDimension(DerivedDimension): """ Dimension symbol representing a non-contiguous sub-region of a given ``parent`` Dimension, which cyclically produces a finite range of values, such as ``0, 1, 2, 0, 1, 2, 0, ...``. :param parent: Parent dimension from which the ModuloDimension is created. :param offset: An integer representing an offset from the parent dimension. :param modulo: The extent of the range. :param name: (Optional) force a name for this Dimension. """ def __new__(cls, parent, offset, modulo, name=None): return ModuloDimension.__xnew_cached_(cls, parent, offset, modulo, name) def __new_stage2__(cls, parent, offset, modulo, name): if name is None: name = cls._genname(parent.name, (offset, modulo)) newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._offset = offset newobj._modulo = modulo return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def offset(self): return self._offset @property def modulo(self): return self._modulo @property def origin(self): return self.parent + self.offset @cached_property def symbolic_start(self): return (self.root + self.offset) % self.modulo symbolic_incr = symbolic_start @property def _properties(self): return (self._offset, self._modulo) def _arg_defaults(self, **kwargs): """ A :class:`ModuloDimension` provides no arguments, so this method returns an empty dict. """ return {} def _arg_values(self, *args, **kwargs): """ A :class:`ModuloDimension` provides no arguments, so there are no argument values to be derived. """ return {}
class FieldPointerSymbol(TypedSymbol): """Sympy symbol representing the pointer to the beginning of the field data.""" def __new__(cls, *args, **kwds): obj = FieldPointerSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, field_name, field_dtype, const): name = f"_data_{field_name}" dtype = PointerType(get_base_type(field_dtype), const=const, restrict=True) obj = super(FieldPointerSymbol, cls).__xnew__(cls, name, dtype) obj.field_name = field_name return obj def __getnewargs__(self): return self.field_name, self.dtype, self.dtype.const def __getnewargs_ex__(self): return (self.field_name, self.dtype, self.dtype.const), {} def _hashable_content(self): return super()._hashable_content(), self.field_name __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__))
class TypedSymbol(sp.Symbol): def __new__(cls, *args, **kwds): obj = TypedSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, name, dtype, *args, **kwargs): obj = super(TypedSymbol, cls).__xnew__(cls, name, *args, **kwargs) try: obj._dtype = create_type(dtype) except (TypeError, ValueError): # on error keep the string obj._dtype = dtype return obj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def dtype(self): return self._dtype def _hashable_content(self): return super()._hashable_content(), hash(self._dtype) def __getnewargs__(self): return self.name, self.dtype # For reference: Numpy type hierarchy https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.scalars.html @property def is_integer(self): if hasattr(self.dtype, 'numpy_dtype'): return np.issubdtype(self.dtype.numpy_dtype, np.integer) or super().is_integer else: return super().is_integer @property def is_negative(self): if hasattr(self.dtype, 'numpy_dtype'): if np.issubdtype(self.dtype.numpy_dtype, np.unsignedinteger): return False return super().is_negative @property def is_nonnegative(self): if self.is_negative is False: return True else: return super().is_nonnegative @property def is_real(self): if hasattr(self.dtype, 'numpy_dtype'): return np.issubdtype(self.dtype.numpy_dtype, np.integer) or \ np.issubdtype(self.dtype.numpy_dtype, np.floating) or \ super().is_real else: return super().is_real
class IncrDimension(DerivedDimension): """ Dimension symbol representing a non-contiguous sub-region of a given ``parent`` Dimension, with one point every ``step`` points. Thus, if ``step == k``, the dimension represents the sequence ``start, start + k, start + 2*k, ...``. :param parent: Parent dimension from which the IncrDimension is created. :param start: An integer representing the starting point of the sequence. :param step: The distance between two consecutive points. :param name: (Optional) force a name for this Dimension. """ def __new__(cls, parent, start, step, name=None): return IncrDimension.__xnew_cached_(cls, parent, start, step, name) def __new_stage2__(cls, parent, start, step, name): if name is None: name = cls._genname(parent.name, (start, step)) newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._start = start newobj._step = step return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def step(self): return self._step @cached_property def symbolic_start(self): return self._start @property def symbolic_incr(self): return self + self.step @property def _properties(self): return (self._start, self._step) def _arg_defaults(self, **kwargs): """ A :class:`IncrDimension` provides no arguments, so this method returns an empty dict. """ return {} def _arg_values(self, *args, **kwargs): """ A :class:`IncrDimension` provides no arguments, so there are no argument values to be derived. """ return {} # Pickling support _pickle_args = ['parent', 'symbolic_start', 'step'] _pickle_kwargs = ['name']
class ImmutableSparseMatrix(SparseMatrix, Basic): """Create an immutable version of a sparse matrix. Examples ======== >>> from sympy import eye >>> from sympy.matrices.immutable import ImmutableSparseMatrix >>> ImmutableSparseMatrix(1, 1, {}) Matrix([[0]]) >>> ImmutableSparseMatrix(eye(3)) Matrix([ [1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> _[0, 0] = 42 Traceback (most recent call last): ... TypeError: Cannot set values of ImmutableSparseMatrix >>> _.shape (3, 3) """ is_Matrix = True _class_priority = 9 @classmethod def _new(cls, *args, **kwargs): s = MutableSparseMatrix(*args) rows = Integer(s.rows) cols = Integer(s.cols) mat = Dict(s._smat) obj = Basic.__new__(cls, rows, cols, mat) obj.rows = s.rows obj.cols = s.cols obj._smat = s._smat return obj def __new__(cls, *args, **kwargs): return cls._new(*args, **kwargs) def __setitem__(self, *args): raise TypeError("Cannot set values of ImmutableSparseMatrix") def __hash__(self): return hash((type(self).__name__, ) + (self.shape, tuple(self._smat))) _eval_Eq = ImmutableDenseMatrix._eval_Eq def as_immutable(self): return self def is_diagonalizable(self, reals_only=False, **kwargs): return super(ImmutableSparseMatrix, self).is_diagonalizable(reals_only=reals_only, **kwargs) is_diagonalizable.__doc__ = SparseMatrix.is_diagonalizable.__doc__ is_diagonalizable = cacheit(is_diagonalizable)
class ConditionalDimension(DerivedDimension): is_NonlinearDerived = True is_Conditional = True """ Dimension symbol representing a sub-region of a given ``parent`` Dimension. Unlike a :class:`SubDimension`, a ConditionalDimension does not represent a contiguous region. The iterations touched by a ConditionalDimension are expressible in two different ways: :: * ``factor``: an integer indicating the size of the increment. * ``condition``: an arbitrary SymPy expression depending on ``parent``. All iterations for which the expression evaluates to True are part of the ``SubDimension`` region. ConditionalDimension needs runtime arguments. The generated C code will require the size of the dimension to initialize the arrays as e.g: .. code-block:: python x = grid.dimension[0] x1 = ConditionalDimension(name='x1', parent=x, factor=2) u1 = TimeFunction(name='u1', dimensions=(x1,), size=grid.shape[0]/factor) # The generated code will look like float (*restrict u1)[x1_size + 1] = """ def __new__(cls, name, parent, factor=None, condition=None): return ConditionalDimension.__xnew_cached_(cls, name, parent, factor, condition) def __new_stage2__(cls, name, parent, factor, condition): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._factor = factor newobj._condition = condition return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def spacing(self): return self.factor * self.parent.spacing @property def factor(self): return self._factor @property def condition(self): return self._condition @property def _properties(self): return (self._factor, self._condition) # Pickling support _pickle_kwargs = DerivedDimension._pickle_kwargs + ['factor', 'condition']
class TypedSymbol(sp.Symbol): def __new__(cls, *args, **kwds): obj = TypedSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, name, dtype, **kwargs): assumptions = assumptions_from_dtype(dtype) assumptions.update(kwargs) obj = super(TypedSymbol, cls).__xnew__(cls, name, **assumptions) try: obj._dtype = create_type(dtype) except (TypeError, ValueError): # on error keep the string obj._dtype = dtype return obj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def dtype(self): return self._dtype def _hashable_content(self): return super()._hashable_content(), hash(self._dtype) def __getnewargs__(self): return self.name, self.dtype def __getnewargs_ex__(self): return (self.name, self.dtype), self.assumptions0 @property def canonical(self): return self @property def reversed(self): return self @property def headers(self): headers = [] try: if np.issubdtype(self.dtype.numpy_dtype, np.complexfloating): headers.append('"cuda_complex.hpp"') except Exception: pass try: if np.issubdtype(self.dtype.base_type.numpy_dtype, np.complexfloating): headers.append('"cuda_complex.hpp"') except Exception: pass return headers
def _cacheit(func): """Cache functions if `CacheContext` is enabled.""" cfunc = cache.cacheit(func) def cached_func(*args, **kwargs): if context.Cache.current_context: return cfunc(*args, **kwargs) else: return func(*args, **kwargs) return cached_func
class ThreadIndexingSymbol(TypedSymbol): def __new__(cls, *args, **kwds): obj = ThreadIndexingSymbol.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, name, dtype, *args, **kwargs): obj = super(ThreadIndexingSymbol, cls).__xnew__(cls, name, dtype, *args, **kwargs) return obj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__))
class Signal(Symbol): """ Signal field, in the sense of non-linear response theory. This is a specialization of the basic sympy Symbol class. Parameters ---------- k : list of ints List of wavevector interactions. A third order response would have a list of 3 number. The number is the pulse index and the sign designates the direction, ie: <0 implies complex conjugate. Thus: [-1, 2, 3] is the signal due to -k_1+k_2+k_3, the photon echo [-1, 1, 3] is the signal due to -k_1+k_1+k_3, a pump-probe signal [1] is the linear signal due to the first pulse, on the ket side. Note ---- This is designed to be used by the `signal` function. """ # with help from: # https://groups.google.com/forum/#!topic/sympy/pU81Trc_Xr8 def __new_stage2__(cls, name, k, *a, **kw): obj = super(Signal, cls).__xnew__(cls, name, *a, **kw) obj.k = k return obj def __new__(cls, k, *a, **kw): s = Signal._mk_symbol(k) obj = Signal.__xnew_cached_(cls, s, k, *a, **kw) return obj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) # _hashable_content should be fine. # (The values of k are included in the symbol.) @property def order(self): return len(self.k) @staticmethod def _mk_symbol(k): """ Make the string to be used as a symbol, eg: '\chi_{k_1+k_2-k_3}' """ lbl = ['+' if i > 0 else '-' for i in k] lbl = [s + 'k_' + str(abs(n)) for s, n in zip(lbl, k)] return r'\chi_{' + ''.join(lbl) + '}'
class CeMoment(sp.Symbol): def __new__(cls, name, *args, **kwargs): obj = CeMoment.__xnew_cached_(cls, name, *args, **kwargs) return obj def __new_stage2__(self, name, moment_tuple, superscript=-1): obj = super(CeMoment, self).__xnew__(self, name) obj._moment_tuple = moment_tuple while len(obj._moment_tuple) < 3: obj._moment_tuple = obj._moment_tuple + (0, ) obj.superscript = superscript return obj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def _hashable_content(self): super_class_contents = list(super(CeMoment, self)._hashable_content()) return tuple( super_class_contents + [hash(repr(self.moment_tuple)), hash(repr(self.superscript))]) @property def indices(self): return get_moment_indices(self.moment_tuple) @property def moment_tuple(self): return self._moment_tuple def __getnewargs__(self): return self.name, self.moment_tuple, self.superscript def _latex(self, *_): coord_str = [] for i, comp in enumerate(self.moment_tuple): coord_str += [str(i)] * comp coord_str = "".join(coord_str) result = "{%s_{%s}" % (self.name, coord_str) if self.superscript >= 0: result += "^{(%d)}}" % (self.superscript, ) else: result += "}" return result def __repr__(self): return "%s_(%d)_%s" % (self.name, self.superscript, self.moment_tuple) def __str__(self): return "%s_(%d)_%s" % (self.name, self.superscript, self.moment_tuple)
class CFunction(TypedSymbol): def __new__(cls, function, dtype): return CFunction.__xnew_cached_(cls, function, dtype) def __new_stage2__(cls, function, dtype): return super(CFunction, cls).__xnew__(cls, function, dtype) __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def __getnewargs__(self): return self.name, self.dtype def __getnewargs_ex__(self): return (self.name, self.dtype), {}
class ImmutableRepMatrix(RepMatrix, MatrixExpr): # type: ignore """Immutable matrix based on RepMatrix Uses DomainMAtrix as the internal representation. """ # # This is a subclass of RepMatrix that adds/overrides some methods to make # the instances Basic and immutable. ImmutableRepMatrix is a superclass for # both ImmutableDenseMatrix and ImmutableSparseMatrix. # def __new__(cls, *args, **kwargs): return cls._new(*args, **kwargs) __hash__ = MatrixExpr.__hash__ def copy(self): return self @property def cols(self): return self._cols @property def rows(self): return self._rows @property def shape(self): return self._rows, self._cols def as_immutable(self): return self def _entry(self, i, j, **kwargs): return self[i, j] def __setitem__(self, *args): raise TypeError("Cannot set values of {}".format(self.__class__)) def is_diagonalizable(self, reals_only=False, **kwargs): return super().is_diagonalizable(reals_only=reals_only, **kwargs) is_diagonalizable.__doc__ = SparseRepMatrix.is_diagonalizable.__doc__ is_diagonalizable = cacheit(is_diagonalizable)
class TypedImaginaryUnit(TypedSymbol): def __new__(cls, *args, **kwds): obj = TypedImaginaryUnit.__xnew_cached_(cls, *args, **kwds) return obj def __new_stage2__(cls, dtype): obj = super(TypedImaginaryUnit, cls).__xnew__(cls, "_i", dtype, imaginary=True) return obj headers = ['"cuda_complex.hpp"'] __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def __getnewargs__(self): return (self.dtype,) def __getnewargs_ex__(self): return (self.dtype,), {}
class SubDimension(DerivedDimension): """ Symbol defining a convex iteration sub-space derived from a ``parent`` Dimension. Parameters ---------- name : str Name of the dimension. parent : Dimension The parent Dimension. left : expr-like Symbolic expression providing the left (lower) bound of the SubDimension. right : expr-like Symbolic expression providing the right (upper) bound of the SubDimension. thickness : 2-tuple of 2-tuples The thickness of the left and right regions, respectively. local : bool True if, in case of domain decomposition, the SubDimension is guaranteed not to span more than one domains, False otherwise. Examples -------- SubDimensions should *not* be created directly in user code; SubDomains should be used instead. Exceptions are rare. To create a SubDimension, one should use the shortcut methods ``left``, ``right``, ``middle``. For example, to create a SubDimension that spans the entire space of the parent Dimension except for the two extremes: >>> from devito import Dimension, SubDimension >>> x = Dimension('x') >>> xi = SubDimension.middle('xi', x, 1, 1) For a SubDimension that only spans the three leftmost points of its parent Dimension, instead: >>> xl = SubDimension.left('xl', x, 3) SubDimensions created via the ``left`` and ``right`` shortcuts are, by default, local (i.e., non-distributed) Dimensions, as they are assumed to fit entirely within a single domain. This is the most typical use case (e.g., to set up boundary conditions). To drop this assumption, pass ``local=False``. """ is_Sub = True def __new__(cls, name, parent, left, right, thickness, local): return SubDimension.__xnew_cached_(cls, name, parent, left, right, thickness, local) def __new_stage2__(cls, name, parent, left, right, thickness, local): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._interval = sympy.Interval(left, right) newobj._thickness = cls._Thickness(*thickness) newobj._local = local return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) _Thickness = namedtuple('Thickness', 'left right') @classmethod def left(cls, name, parent, thickness, local=True): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_min, right=parent.symbolic_min + lst - 1, thickness=((lst, thickness), (rst, 0)), local=local) @classmethod def right(cls, name, parent, thickness, local=True): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_max - rst + 1, right=parent.symbolic_max, thickness=((lst, 0), (rst, thickness)), local=local) @classmethod def middle(cls, name, parent, thickness_left, thickness_right, local=False): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_min + lst, right=parent.symbolic_max - rst, thickness=((lst, thickness_left), (rst, thickness_right)), local=local) @property def symbolic_min(self): return self._interval.left @property def symbolic_max(self): return self._interval.right @property def local(self): return self._local @property def thickness(self): return self._thickness @property def _maybe_distributed(self): return not self.local @classmethod def _symbolic_thickness(cls, name): return (Scalar(name="%s_ltkn" % name, dtype=np.int32, is_const=True), Scalar(name="%s_rtkn" % name, dtype=np.int32, is_const=True)) @cached_property def _thickness_map(self): return dict(self.thickness) def _offset_left(self): # The left extreme of the SubDimension can be related to either the # min or max of the parent dimension try: symbolic_thickness = self.symbolic_min - self.parent.symbolic_min val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_min except TypeError: symbolic_thickness = self.symbolic_min - self.parent.symbolic_max val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_max def _offset_right(self): # The right extreme of the SubDimension can be related to either the # min or max of the parent dimension try: symbolic_thickness = self.symbolic_max - self.parent.symbolic_min val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_min except TypeError: symbolic_thickness = self.symbolic_max - self.parent.symbolic_max val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_max @property def _properties(self): return (self._interval, self.thickness, self.local) def _arg_defaults(self, grid=None, **kwargs): if grid is not None and grid.is_distributed(self.root): # Get local thickness ltkn = grid.distributor.glb_to_loc(self.root, self.thickness.left[1], LEFT) rtkn = grid.distributor.glb_to_loc(self.root, self.thickness.right[1], RIGHT) return { i.name: v for i, v in zip(self._thickness_map, (ltkn, rtkn)) } else: return {k.name: v for k, v in self.thickness} def _arg_values(self, args, interval, grid, **kwargs): return self._arg_defaults(grid=grid, **kwargs) # Pickling support _pickle_args = DerivedDimension._pickle_args +\ ['symbolic_min', 'symbolic_max', 'thickness', 'local'] _pickle_kwargs = []
class ImmutableSparseMatrix(SparseMatrix, MatrixExpr): """Create an immutable version of a sparse matrix. Examples ======== >>> from sympy import eye >>> from sympy.matrices.immutable import ImmutableSparseMatrix >>> ImmutableSparseMatrix(1, 1, {}) Matrix([[0]]) >>> ImmutableSparseMatrix(eye(3)) Matrix([ [1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> _[0, 0] = 42 Traceback (most recent call last): ... TypeError: Cannot set values of ImmutableSparseMatrix >>> _.shape (3, 3) """ is_Matrix = True _class_priority = 9 def __new__(cls, *args, **kwargs): return cls._new(*args, **kwargs) __hash__ = MatrixExpr.__hash__ @classmethod def _new(cls, *args, **kwargs): rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs) obj = Basic.__new__(cls, Integer(rows), Integer(cols), Dict(smat)) obj._rows = rows obj._cols = cols obj._smat = smat return obj def __setitem__(self, *args): raise TypeError("Cannot set values of ImmutableSparseMatrix") def _entry(self, i, j, **kwargs): return SparseMatrix.__getitem__(self, (i, j)) _eval_Eq = ImmutableDenseMatrix._eval_Eq @property def cols(self): return self._cols @property def rows(self): return self._rows @property def shape(self): return self._rows, self._cols def as_immutable(self): return self def is_diagonalizable(self, reals_only=False, **kwargs): return super().is_diagonalizable( reals_only=reals_only, **kwargs) is_diagonalizable.__doc__ = SparseMatrix.is_diagonalizable.__doc__ is_diagonalizable = cacheit(is_diagonalizable)
class ModuloDimension(DerivedDimension): """ Dimension symbol representing a non-contiguous sub-region of a given ``parent`` Dimension, which cyclically produces a finite range of values, such as ``0, 1, 2, 0, 1, 2, 0, ...``. Parameters ---------- parent : Dimension The Dimension from which the ModuloDimension is derived. offset : int The offset from the parent dimension modulo : int The divisor value. name : str, optional To force a different Dimension name. Notes ----- This type should not be instantiated directly in user code; if in need for modulo buffered iteration, use SteppingDimension instead. """ is_Modulo = True def __new__(cls, parent, offset, modulo, name=None): return ModuloDimension.__xnew_cached_(cls, parent, offset, modulo, name) def __new_stage2__(cls, parent, offset, modulo, name): if name is None: name = cls._genname(parent.name, (offset, modulo)) newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._offset = offset newobj._modulo = modulo return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def offset(self): return self._offset @property def modulo(self): return self._modulo @property def origin(self): return self.parent + self.offset @cached_property def symbolic_min(self): return (self.root + self.offset) % self.modulo symbolic_incr = symbolic_min @property def _properties(self): return (self._offset, self._modulo) def _arg_defaults(self, **kwargs): """ A ModuloDimension provides no arguments, so this method returns an empty dict. """ return {} def _arg_values(self, *args, **kwargs): """ A ModuloDimension provides no arguments, so there are no argument values to be derived. """ return {} # Pickling support _pickle_args = ['parent', 'offset', 'modulo'] _pickle_kwargs = ['name']
class IncrDimension(DerivedDimension): """ Dimension symbol representing a non-contiguous sub-region of a given ``parent`` Dimension, with one point every ``step`` points. Thus, if ``step == k``, the dimension represents the sequence ``min, min + k, min + 2*k, ...``. Parameters ---------- parent : Dimension The Dimension from which the IncrDimension is derived. _min : int, optional The minimum point of the sequence. Defaults to the parent's symbolic minimum. step : int, optional The distance between two consecutive points. Defaults to the symbolic size. name : str, optional To force a different Dimension name. Notes ----- This type should not be instantiated directly in user code. """ is_Incr = True def __new__(cls, parent, _min=None, step=None, name=None): return IncrDimension.__xnew_cached_(cls, parent, _min, step, name) def __new_stage2__(cls, parent, _min, step, name): if name is None: name = cls._genname(parent.name, (_min, step)) newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._min = _min newobj._step = step return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @cached_property def step(self): return self._step if self._step is not None else self.symbolic_size @cached_property def max_step(self): return self.parent.symbolic_max - self.parent.symbolic_min + 1 @cached_property def symbolic_min(self): return self._min if self._min is not None else self.parent.symbolic_min @property def symbolic_incr(self): return self + self.step @property def _properties(self): return (self._min, self._step) def _arg_defaults(self, **kwargs): """ An IncrDimension provides no arguments, so this method returns an empty dict. """ return {} def _arg_values(self, *args, **kwargs): """ An IncrDimension provides no arguments, so there are no argument values to be derived. """ return {} # Pickling support _pickle_args = ['parent', 'symbolic_min', 'step'] _pickle_kwargs = ['name']
class SubDimension(DerivedDimension): """ Symbol defining a convex iteration sub-space derived from a ``parent`` Dimension. Parameters ---------- name : str Name of the dimension. parent : Dimension The parent Dimension. left : expr-like Symbolic expression providing the left (lower) bound of the SubDimension. right : expr-like Symbolic expression providing the right (upper) bound of the SubDimension. thickness : 2-tuple of 2-tuples The thickness of the left and right regions, respectively. Examples -------- Apart from rare circumstances, SubDimensions should *not* be created directly in user code; SubDomains should be used instead. To create a SubDimension, one typically uses the shortcut methods ``left``, ``right``, ``middle``. For example, to create a SubDimension that spans the entire space of the parent Dimension except for the two extremes, one could proceed as follows >>> from devito import Dimension, SubDimension >>> x = Dimension('x') >>> xi = SubDimension.middle('xi', x, 1, 1) For a SubDimension that only spans the three leftmost points of its parent Dimension, instead >>> xl = SubDimension.left('xl', x, 3) """ is_Sub = True def __new__(cls, name, parent, left, right, thickness): return SubDimension.__xnew_cached_(cls, name, parent, left, right, thickness) def __new_stage2__(cls, name, parent, left, right, thickness): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._interval = sympy.Interval(left, right) newobj._thickness = cls._Thickness(*thickness) return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) _Thickness = namedtuple('Thickness', 'left right') @classmethod def left(cls, name, parent, thickness): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_min, right=parent.symbolic_min + lst - 1, thickness=((lst, thickness), (rst, 0))) @classmethod def right(cls, name, parent, thickness): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_max - rst + 1, right=parent.symbolic_max, thickness=((lst, 0), (rst, thickness))) @classmethod def middle(cls, name, parent, thickness_left, thickness_right): lst, rst = cls._symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_min + lst, right=parent.symbolic_max - rst, thickness=((lst, thickness_left), (rst, thickness_right))) @property def symbolic_min(self): return self._interval.left @property def symbolic_max(self): return self._interval.right @property def thickness(self): return self._thickness @classmethod def _symbolic_thickness(cls, name): return (Scalar(name="%s_ltkn" % name, dtype=np.int32, is_const=True), Scalar(name="%s_rtkn" % name, dtype=np.int32, is_const=True)) @cached_property def _thickness_map(self): return dict(self.thickness) def _offset_left(self): # The left extreme of the SubDimension can be related to either the # min or max of the parent dimension try: symbolic_thickness = self.symbolic_min - self.parent.symbolic_min val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_min except TypeError: symbolic_thickness = self.symbolic_min - self.parent.symbolic_max val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_max def _offset_right(self): # The right extreme of the SubDimension can be related to either the # min or max of the parent dimension try: symbolic_thickness = self.symbolic_max - self.parent.symbolic_min val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_min except TypeError: symbolic_thickness = self.symbolic_max - self.parent.symbolic_max val = symbolic_thickness.subs(self._thickness_map) return int(val), self.parent.symbolic_max @property def _properties(self): return (self._interval, self.thickness) def _arg_defaults(self, grid=None, **kwargs): if grid is not None and grid.is_distributed(self.root): # Get local thickness ltkn = grid.distributor.glb_to_loc(self.root, self.thickness.left[1], LEFT) rtkn = grid.distributor.glb_to_loc(self.root, self.thickness.right[1], RIGHT) return { i.name: v for i, v in zip(self._thickness_map, (ltkn, rtkn)) } else: return {k.name: v for k, v in self.thickness} def _arg_values(self, args, interval, grid, **kwargs): return self._arg_defaults(grid=grid, **kwargs) # Pickling support _pickle_args = DerivedDimension._pickle_args +\ ['symbolic_min', 'symbolic_max', 'thickness'] _pickle_kwargs = []
class ConditionalDimension(DerivedDimension): """ Symbol defining a non-convex iteration sub-space derived from a ``parent`` Dimension, implemented by the compiler generating conditional "if-then" code within the parent Dimension's iteration space. Parameters ---------- name : str Name of the dimension. parent : Dimension The parent Dimension. factor : int, optional The number of iterations between two executions of the if-branch. If None (default), ``condition`` must be provided. condition : expr-like, optional An arbitrary SymPy expression, typically involving the ``parent`` Dimension. When it evaluates to True, the if-branch is executed. If None (default), ``factor`` must be provided. indirect : bool, optional If True, use ``condition``, rather than the parent Dimension, to index into arrays. A typical use case is when arrays are accessed indirectly via the ``condition`` expression. Defaults to False. Examples -------- Among the other things, ConditionalDimensions are indicated to implement Function subsampling. In the following example, an Operator evaluates the Function ``g`` and saves its content into ``f`` every ``factor=4`` iterations. >>> from devito import Dimension, ConditionalDimension, Function, Eq, Operator >>> size, factor = 16, 4 >>> i = Dimension(name='i') >>> ci = ConditionalDimension(name='ci', parent=i, factor=factor) >>> g = Function(name='g', shape=(size,), dimensions=(i,)) >>> f = Function(name='f', shape=(size/factor,), dimensions=(ci,)) >>> op = Operator([Eq(g, 1), Eq(f, g)]) The Operator generates the following for-loop (pseudocode) .. code-block:: C for (int i = i_m; i <= i_M; i += 1) { g[i] = 1; if (i%4 == 0) { f[i / 4] = g[i]; } } Another typical use case is when one needs to constrain the execution of loop iterations to make sure certain conditions are honoured. The following artificial example employs indirect array accesses and uses ConditionalDimension to guard against out-of-bounds accesses. >>> from sympy import And >>> ci = ConditionalDimension(name='ci', parent=i, ... condition=And(g[i] > 0, g[i] < 4, evaluate=False)) >>> f = Function(name='f', shape=(size/factor,), dimensions=(ci,)) >>> op = Operator(Eq(f[g[i]], f[g[i]] + 1)) The Operator generates the following for-loop (pseudocode) .. code-block:: C for (int i = i_m; i <= i_M; i += 1) { if (g[i] > 0 && g[i] < 4) { f[g[i]] = f[g[i]] + 1; } } """ is_NonlinearDerived = True is_Conditional = True def __new__(cls, name, parent, factor=None, condition=None, indirect=False): return ConditionalDimension.__xnew_cached_(cls, name, parent, factor, condition, indirect) def __new_stage2__(cls, name, parent, factor, condition, indirect): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._factor = factor newobj._condition = condition newobj._indirect = indirect return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def spacing(self): return self.factor * self.parent.spacing @property def factor(self): """""" return self._factor if self._factor is not None else 1 @property def condition(self): return self._condition @property def indirect(self): return self._indirect @property def index(self): return self if self.indirect is True else self.parent @property def _properties(self): return (self._factor, self._condition, self._indirect) # Pickling support _pickle_kwargs = DerivedDimension._pickle_kwargs + [ 'factor', 'condition', 'indirect' ]
class Dimension(AbstractSymbol, ArgProvider): """ Symbol defining an iteration space. A Dimension represents a problem dimension. It is typically used to index into Functions, but it can also appear in the middle of a symbolic expression just like any other symbol. Parameters ---------- name : str Name of the dimension. spacing : symbol, optional A symbol to represent the physical spacing along this Dimension. Examples -------- Dimensions are automatically created when a Grid is instantiated. >>> from devito import Grid >>> grid = Grid(shape=(4, 4)) >>> x, y = grid.dimensions >>> type(x) <class 'devito.types.dimension.SpaceDimension'> >>> time = grid.time_dim >>> type(time) <class 'devito.types.dimension.TimeDimension'> >>> t = grid.stepping_dim >>> type(t) <class 'devito.types.dimension.SteppingDimension'> Alternatively, one can create Dimensions explicitly >>> from devito import Dimension >>> i = Dimension(name='i') Or, when many "free" Dimensions are needed, with the shortcut >>> from devito import dimensions >>> i, j, k = dimensions('i j k') A Dimension can be used to build a Function as well as within symbolic expressions, as both array index ("indexed notation") and free symbol. >>> from devito import Function >>> f = Function(name='f', shape=(4, 4), dimensions=(i, j)) >>> f + f 2*f(i, j) >>> f[i + 1, j] + f[i, j + 1] f[i, j + 1] + f[i + 1, j] >>> f*i i*f(i, j) """ is_Dimension = True is_Space = False is_Time = False is_Default = False is_Derived = False is_NonlinearDerived = False is_Sub = False is_Conditional = False is_Stepping = False is_Modulo = False is_Incr = False # Unlike other Symbols, Dimensions can only be integers dtype = np.int32 _C_typename = 'const %s' % dtype_to_cstr(dtype) _C_typedata = _C_typename def __new__(cls, name, spacing=None): return Dimension.__xnew_cached_(cls, name, spacing) def __new_stage2__(cls, name, spacing=None): newobj = sympy.Symbol.__xnew__(cls, name) newobj._spacing = spacing or Scalar(name='h_%s' % name, is_const=True) return newobj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def __str__(self): return self.name @property def spacing(self): """Symbol representing the physical spacing along the Dimension.""" return self._spacing @cached_property def symbolic_size(self): """Symbolic size of the Dimension.""" return Scalar(name=self.size_name, dtype=np.int32, is_const=True) @cached_property def symbolic_min(self): """Symbol defining the minimum point of the Dimension.""" return Scalar(name=self.min_name, dtype=np.int32, is_const=True) @cached_property def symbolic_max(self): """Symbol defining the maximum point of the Dimension.""" return Scalar(name=self.max_name, dtype=np.int32, is_const=True) @cached_property def size_name(self): return "%s_size" % self.name @cached_property def min_name(self): return "%s_m" % self.name @cached_property def max_name(self): return "%s_M" % self.name @property def root(self): return self @property def _limits(self): return (self.symbolic_min, self.symbolic_max, 1) @property def _C_name(self): return self.name @property def _properties(self): return (self.spacing, ) def _hashable_content(self): return super(Dimension, self)._hashable_content() + self._properties @cached_property def _defines(self): return frozenset({self}) @property def _arg_names(self): """Tuple of argument names introduced by the Dimension.""" return (self.name, self.size_name, self.max_name, self.min_name) def _arg_defaults(self, _min=None, size=None, alias=None): """ A map of default argument values defined by the Dimension. Parameters ---------- _min : int, optional Minimum point as provided by data-carrying objects. size : int, optional Size as provided by data-carrying symbols. alias : Dimension, optional To get the min/max/size names under which to store values. Use self's if None. """ dim = alias or self return { dim.min_name: _min or 0, dim.size_name: size, dim.max_name: size if size is None else size - 1 } def _arg_values(self, args, interval, grid, **kwargs): """ Produce a map of argument values after evaluating user input. If no user input is provided, get a known value in ``args`` and adjust it so that no out-of-bounds memory accesses will be performeed. The adjustment exploits the information in ``interval``, an Interval describing the Dimension data space. If no value is available in ``args``, use a default value. Parameters ---------- args : dict Known argument values. interval : Interval Description of the Dimension data space. grid : Grid Only relevant in case of MPI execution; if ``self`` is a distributed Dimension, then ``grid`` is used to translate user input into rank-local indices. **kwargs Dictionary of user-provided argument overrides. """ # Fetch user input and convert into rank-local values glb_minv = kwargs.pop(self.min_name, None) glb_maxv = kwargs.pop(self.max_name, kwargs.pop(self.name, None)) if grid is not None and grid.is_distributed(self): loc_minv, loc_maxv = grid.distributor.glb_to_loc( self, (glb_minv, glb_maxv)) else: loc_minv, loc_maxv = glb_minv, glb_maxv # If no user-override provided, use a suitable default value defaults = self._arg_defaults() if glb_minv is None: loc_minv = args.get(self.min_name, defaults[self.min_name]) try: loc_minv -= min(interval.lower, 0) except (AttributeError, TypeError): pass if glb_maxv is None: loc_maxv = args.get(self.max_name, defaults[self.max_name]) try: loc_maxv -= max(interval.upper, 0) except (AttributeError, TypeError): pass return {self.min_name: loc_minv, self.max_name: loc_maxv} def _arg_check(self, args, size, interval): """ Raises ------ InvalidArgument If any of the ``self``-related runtime arguments in ``args`` will cause an out-of-bounds access. """ if self.min_name not in args: raise InvalidArgument("No runtime value for %s" % self.min_name) if interval.is_Defined and args[self.min_name] + interval.lower < 0: raise InvalidArgument("OOB detected due to %s=%d" % (self.min_name, args[self.min_name])) if self.max_name not in args: raise InvalidArgument("No runtime value for %s" % self.max_name) if interval.is_Defined and args[self.max_name] + interval.upper >= size: raise InvalidArgument("OOB detected due to %s=%d" % (self.max_name, args[self.max_name])) # Allow the specific case of max=min-1, which disables the loop if args[self.max_name] < args[self.min_name] - 1: raise InvalidArgument("Illegal max=%s < min=%s" % (args[self.max_name], args[self.min_name])) elif args[self.max_name] == args[self.min_name] - 1: debug( "%s=%d and %s=%d might cause no iterations along Dimension %s", self.min_name, args[self.min_name], self.max_name, args[self.max_name], self.name) # Pickling support _pickle_args = ['name'] _pickle_kwargs = ['spacing'] __reduce_ex__ = Pickable.__reduce_ex__
class DerivedDimension(Dimension): """ Symbol defining an iteration space derived from a ``parent`` Dimension. Parameters ---------- name : str Name of the dimension. parent : Dimension The parent Dimension. """ is_Derived = True _keymap = {} """Map all seen instance `_properties` to a unique number. This is used to create unique Dimension names.""" def __new__(cls, name, parent): return DerivedDimension.__xnew_cached_(cls, name, parent) def __new_stage2__(cls, name, parent): assert isinstance(parent, Dimension) newobj = sympy.Symbol.__xnew__(cls, name) newobj._parent = parent # Inherit time/space identifiers newobj.is_Time = parent.is_Time newobj.is_Space = parent.is_Space return newobj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @classmethod def _gensuffix(cls, key): return cls._keymap.setdefault(key, len(cls._keymap)) @classmethod def _genname(cls, prefix, key): return "%s%d" % (prefix, cls._gensuffix(key)) @property def parent(self): return self._parent @property def root(self): return self._parent.root @property def spacing(self): return self.parent.spacing @property def _properties(self): return () def _hashable_content(self): return (self.name, self.parent._hashable_content()) + self._properties @cached_property def _defines(self): return frozenset({self}) | self.parent._defines @property def _arg_names(self): return self.parent._arg_names def _arg_check(self, *args): """A DerivedDimension performs no runtime checks.""" return # Pickling support _pickle_args = Dimension._pickle_args + ['parent'] _pickle_kwargs = []
class Dimension(AbstractSymbol, ArgProvider): is_Dimension = True is_Space = False is_Time = False is_Default = False is_Derived = False is_NonlinearDerived = False is_Sub = False is_Conditional = False is_Stepping = False is_Modulo = False is_Incr = False """ A Dimension is a symbol representing a problem dimension and thus defining a potential iteration space. :param name: Name of the dimension symbol. :param spacing: Optional, symbol for the spacing along this dimension. """ def __new__(cls, name, spacing=None): return Dimension.__xnew_cached_(cls, name, spacing) def __new_stage2__(cls, name, spacing=None): newobj = sympy.Symbol.__xnew__(cls, name) newobj._spacing = spacing or Scalar(name='h_%s' % name) return newobj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) def __str__(self): return self.name @property def dtype(self): # TODO: Do dimensions really need a dtype? return np.int32 @cached_property def symbolic_size(self): """The symbolic size of this dimension.""" return Scalar(name=self.size_name, dtype=np.int32) @cached_property def symbolic_start(self): """ The symbol defining the iteration start for this dimension. """ return Scalar(name=self.min_name, dtype=np.int32) @cached_property def symbolic_end(self): """ The symbol defining the iteration end for this dimension. """ return Scalar(name=self.max_name, dtype=np.int32) @property def limits(self): return (self.symbolic_start, self.symbolic_end, 1) @cached_property def size_name(self): return "%s_size" % self.name @cached_property def min_name(self): return "%s_m" % self.name @cached_property def max_name(self): return "%s_M" % self.name @property def spacing(self): return self._spacing @property def base(self): return self @property def root(self): return self @property def _properties(self): return (self.spacing,) def _hashable_content(self): return super(Dimension, self)._hashable_content() + self._properties @cached_property def _defines(self): return frozenset({self}) @property def _arg_names(self): """Return a tuple of argument names introduced by this dimension.""" return (self.name, self.size_name, self.max_name, self.min_name) def _arg_defaults(self, start=None, size=None, alias=None): """ Returns a map of default argument values defined by this dimension. :param start: (Optional) known starting point as provided by data-carrying symbols. :param size: (Optional) known size as provided by data-carrying symbols. :param alias: (Optional) name under which to store values. """ dim = alias or self return {dim.min_name: start or 0, dim.size_name: size, dim.max_name: size if size is None else size-1} def _arg_values(self, args, interval, grid, **kwargs): """ Returns a map of argument values after evaluating user input. If no user input is provided, get a known value in ``args`` and adjust it so that no out-of-bounds memory accesses will be performeed. The adjustment exploits the information in ``interval``, a :class:`Interval` describing the data space of this dimension. If there is no known value in ``args``, use a default value. :param args: Dictionary of known argument values. :param interval: A :class:`Interval` for ``self``. :param grid: A :class:`Grid`; if ``self`` is a distributed Dimension in ``grid``, then the user input is translated into rank-local indices. :param kwargs: Dictionary of user-provided argument overrides. """ # Fetch user input and convert into rank-local values glb_minv = kwargs.pop(self.min_name, None) glb_maxv = kwargs.pop(self.max_name, kwargs.pop(self.name, None)) if grid is not None and grid.is_distributed(self): loc_minv, loc_maxv = grid.distributor.glb_to_loc(self, (glb_minv, glb_maxv)) else: loc_minv, loc_maxv = glb_minv, glb_maxv # If no user-override provided, use a suitable default value defaults = self._arg_defaults() if glb_minv is None: loc_minv = args.get(self.min_name, defaults[self.min_name]) try: loc_minv -= min(interval.lower, 0) except (AttributeError, TypeError): pass if glb_maxv is None: loc_maxv = args.get(self.max_name, defaults[self.max_name]) try: loc_maxv -= max(interval.upper, 0) except (AttributeError, TypeError): pass return {self.min_name: loc_minv, self.max_name: loc_maxv} def _arg_check(self, args, size, interval): """ Raises ------ InvalidArgument If any of the ``self``-related runtime arguments in ``args`` will cause an out-of-bounds access. """ if self.min_name not in args: raise InvalidArgument("No runtime value for %s" % self.min_name) if interval.is_Defined and args[self.min_name] + interval.lower < 0: raise InvalidArgument("OOB detected due to %s=%d" % (self.min_name, args[self.min_name])) if self.max_name not in args: raise InvalidArgument("No runtime value for %s" % self.max_name) if interval.is_Defined and args[self.max_name] + interval.upper >= size: raise InvalidArgument("OOB detected due to %s=%d" % (self.max_name, args[self.max_name])) # Allow the specific case of max=min-1, which disables the loop if args[self.max_name] < args[self.min_name]-1: raise InvalidArgument("Illegal max=%s < min=%s" % (args[self.max_name], args[self.min_name])) elif args[self.max_name] == args[self.min_name]-1: debug("%s=%d and %s=%d might cause no iterations along Dimension %s", self.min_name, args[self.min_name], self.max_name, args[self.max_name], self.name) # Pickling support _pickle_args = ['name'] _pickle_kwargs = ['spacing'] __reduce_ex__ = Pickable.__reduce_ex__
class AbstractSymbol(sympy.Symbol, Basic, Pickable): """ Base class for dimension-free symbols. The sub-hierarchy is structured as follows AbstractSymbol | ------------------------------------- | | AbstractCachedSymbol -------------------- | | | Constant Symbol Dimension | Scalar There are three relevant AbstractSymbol sub-types: :: * Symbol: A generic scalar symbol that can be used to build an equation. It does not carry data. Typically, Symbols are created internally by Devito (e.g., for temporary variables) * Constant: A generic scalar symbol that can be used to build an equation. It carries data (a scalar value). * Dimension: A problem dimension, used to create an iteration space. It may be used to build equations; typically, it is used as an index for an Indexed. Notes ----- Constants are cached by both SymPy and Devito, while Symbols and Dimensions by SymPy only. """ is_AbstractSymbol = True def __new__(cls, name, dtype=np.int32): return AbstractSymbol.__xnew_cached_(cls, name, dtype) def __new_stage2__(cls, name, dtype): newobj = sympy.Symbol.__xnew__(cls, name) newobj._dtype = dtype return newobj __xnew__ = staticmethod(__new_stage2__) __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def dtype(self): """The data type of the object.""" return self._dtype @property def indices(self): return () @property def shape(self): return () @property def ndim(self): return 0 @property def symbolic_shape(self): return () @property def base(self): return self @property def function(self): return self def indexify(self): return self def _subs(self, old, new, **hints): """ This stub allows sympy.Basic.subs to operate on an expression involving devito Scalars. Ordinarily the comparisons between devito subclasses of sympy types are quite strict. """ try: if old.name == self.name: return new except AttributeError: pass return self @property def is_const(self): """ True if the symbol value cannot be modified within an Operator (and thus its value is provided by the user directly from Python-land), False otherwise. """ return False @property def _C_name(self): return self.name @property def _C_typename(self): return '%s%s' % ('const ' if self.is_const else '', dtype_to_cstr(self.dtype)) @property def _C_typedata(self): return dtype_to_cstr(self.dtype) @property def _C_ctype(self): return dtype_to_ctype(self.dtype) # Pickling support _pickle_args = ['name'] _pickle_kwargs = ['dtype'] __reduce_ex__ = Pickable.__reduce_ex__
class ImmutableDenseMatrix(DenseMatrix, MatrixExpr): """Create an immutable version of a matrix. Examples ======== >>> from sympy import eye >>> from sympy.matrices import ImmutableMatrix >>> ImmutableMatrix(eye(3)) Matrix([ [1, 0, 0], [0, 1, 0], [0, 0, 1]]) >>> _[0, 0] = 42 Traceback (most recent call last): ... TypeError: Cannot set values of ImmutableDenseMatrix """ # MatrixExpr is set as NotIterable, but we want explicit matrices to be # iterable _iterable = True _class_priority = 8 _op_priority = 10.001 def __new__(cls, *args, **kwargs): return cls._new(*args, **kwargs) __hash__ = MatrixExpr.__hash__ @classmethod def _new(cls, *args, **kwargs): if len(args) == 1 and isinstance(args[0], ImmutableDenseMatrix): return args[0] if kwargs.get('copy', True) is False: if len(args) != 3: raise TypeError("'copy=False' requires a matrix be initialized as rows,cols,[list]") rows, cols, flat_list = args else: rows, cols, flat_list = cls._handle_creation_inputs(*args, **kwargs) flat_list = list(flat_list) # create a shallow copy rows = Integer(rows) cols = Integer(cols) if not isinstance(flat_list, Tuple): flat_list = Tuple(*flat_list) return Basic.__new__(cls, rows, cols, flat_list) @property def _mat(self): # self.args[2] is a Tuple. Access to the elements # of a tuple are significantly faster than Tuple, # so return the internal tuple. return self.args[2].args def _entry(self, i, j, **kwargs): return DenseMatrix.__getitem__(self, (i, j)) def __setitem__(self, *args): raise TypeError("Cannot set values of {}".format(self.__class__)) def _eval_Eq(self, other): """Helper method for Equality with matrices. Relational automatically converts matrices to ImmutableDenseMatrix instances, so this method only applies here. Returns True if the matrices are definitively the same, False if they are definitively different, and None if undetermined (e.g. if they contain Symbols). Returning None triggers default handling of Equalities. """ if not hasattr(other, 'shape') or self.shape != other.shape: return S.false if isinstance(other, MatrixExpr) and not isinstance( other, ImmutableDenseMatrix): return None diff = self - other return sympify(diff.is_zero) def _eval_extract(self, rowsList, colsList): # self._mat is a Tuple. It is slightly faster to index a # tuple over a Tuple, so grab the internal tuple directly mat = self._mat cols = self.cols indices = (i * cols + j for i in rowsList for j in colsList) return self._new(len(rowsList), len(colsList), Tuple(*(mat[i] for i in indices), sympify=False), copy=False) @property def cols(self): return int(self.args[1]) @property def rows(self): return int(self.args[0]) @property def shape(self): return tuple(int(i) for i in self.args[:2]) def is_diagonalizable(self, reals_only=False, **kwargs): return super(ImmutableDenseMatrix, self).is_diagonalizable( reals_only=reals_only, **kwargs) is_diagonalizable.__doc__ = DenseMatrix.is_diagonalizable.__doc__ is_diagonalizable = cacheit(is_diagonalizable)
class SubDimension(DerivedDimension): is_Sub = True """ Dimension symbol representing a contiguous sub-region of a given ``parent`` Dimension. :param name: Name of the dimension symbol. :param parent: Parent dimension from which the SubDimension is created. :param left: Symbolic expression to provide the left bound. :param right: Symbolic expression to provide the right bound. :param thickness: A 2-tuple of 2-tuples, ``((symbol, int), (symbol, int))``, representing the thickness of the left and right regions, respectively. """ def __new__(cls, name, parent, left, right, thickness): return SubDimension.__xnew_cached_(cls, name, parent, left, right, thickness) def __new_stage2__(cls, name, parent, left, right, thickness): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._interval = sympy.Interval(left, right) newobj._thickness = cls.Thickness(*thickness) return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) Thickness = namedtuple('Thickness', 'left right') @classmethod def left(cls, name, parent, thickness): lst, rst = cls.symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_start, right=parent.symbolic_start+lst-1, thickness=((lst, thickness), (rst, 0))) @classmethod def right(cls, name, parent, thickness): lst, rst = cls.symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_end-rst+1, right=parent.symbolic_end, thickness=((lst, 0), (rst, thickness))) @classmethod def middle(cls, name, parent, thickness_left, thickness_right): lst, rst = cls.symbolic_thickness(name) return cls(name, parent, left=parent.symbolic_start+lst, right=parent.symbolic_end-rst, thickness=((lst, thickness_left), (rst, thickness_right))) @classmethod def symbolic_thickness(cls, name): return (Scalar(name="%s_ltkn" % name, dtype=np.int32), Scalar(name="%s_rtkn" % name, dtype=np.int32)) @property def symbolic_start(self): return self._interval.left @property def symbolic_end(self): return self._interval.right @cached_property def thickness_map(self): return dict(self._thickness) @property def thickness(self): return self._thickness def offset_left(self): # The left extreme of the SubDimension can be related to either the # start or end of the parent dimension try: symbolic_thickness = self.symbolic_start - self.parent.symbolic_start val = symbolic_thickness.subs(self.thickness_map) return int(val), self.parent.symbolic_start except TypeError: symbolic_thickness = self.symbolic_start - self.parent.symbolic_end val = symbolic_thickness.subs(self.thickness_map) return int(val), self.parent.symbolic_end def offset_right(self): # The right extreme of the SubDimension can be related to either the # start or end of the parent dimension try: symbolic_thickness = self.symbolic_end - self.parent.symbolic_start val = symbolic_thickness.subs(self.thickness_map) return int(val), self.parent.symbolic_start except TypeError: symbolic_thickness = self.symbolic_end - self.parent.symbolic_end val = symbolic_thickness.subs(self.thickness_map) return int(val), self.parent.symbolic_end @property def _properties(self): return (self._interval, self._thickness) def _arg_defaults(self, grid=None, **kwargs): if grid is not None and grid.is_distributed(self.root): # Get local thickness ltkn = grid.distributor.glb_to_loc(self.root, self.thickness.left[1], LEFT) rtkn = grid.distributor.glb_to_loc(self.root, self.thickness.right[1], RIGHT) return {i.name: v for i, v in zip(self.thickness_map, (ltkn, rtkn))} else: return {k.name: v for k, v in self.thickness} def _arg_values(self, args, interval, grid, **kwargs): return self._arg_defaults(grid=grid, **kwargs) # Pickling support _pickle_args = DerivedDimension._pickle_args +\ ['symbolic_start', 'symbolic_end', 'thickness'] _pickle_kwargs = []
class ConditionalDimension(DerivedDimension): is_NonlinearDerived = True is_Conditional = True """ Dimension symbol representing a sub-region of a given ``parent`` Dimension. Unlike a :class:`SubDimension`, a ConditionalDimension does not represent a contiguous region. The iterations touched by a ConditionalDimension are expressible in two different ways: :: * ``factor``: an integer indicating the size of the increment. * ``condition``: an arbitrary SymPy expression depending on ``parent``. All iterations for which the expression evaluates to True are part of the ``ConditionalDimension`` region. ConditionalDimension needs runtime arguments. The generated C code will require the size of the dimension to initialize the arrays as e.g: .. code-block:: python x = grid.dimension[0] x1 = ConditionalDimension(name='x1', parent=x, factor=2) u1 = TimeFunction(name='u1', dimensions=(x1,), size=grid.shape[0]/factor) # The generated code will look like float (*restrict u1)[x1_size + 1] = .. note:: Sometimes the ConditionalDimension itself, rather than its parent, needs to be used to index into an array. For example, this may happen when an array is indirectly addressed and the ConditionalDimension's parent doesn't define an affine iteration space. In such a case, one should create the ConditionalDimension with the flag ``indirect=True``. """ def __new__(cls, name, parent, factor=None, condition=None, indirect=False): return ConditionalDimension.__xnew_cached_(cls, name, parent, factor, condition, indirect) def __new_stage2__(cls, name, parent, factor, condition, indirect): newobj = DerivedDimension.__xnew__(cls, name, parent) newobj._factor = factor newobj._condition = condition newobj._indirect = indirect return newobj __xnew_cached_ = staticmethod(cacheit(__new_stage2__)) @property def spacing(self): return self.factor * self.parent.spacing @property def factor(self): return self._factor if self._factor is not None else 1 @property def condition(self): return self._condition @property def indirect(self): return self._indirect @property def index(self): return self if self.indirect is True else self.parent @property def _properties(self): return (self._factor, self._condition, self._indirect) # Pickling support _pickle_kwargs = DerivedDimension._pickle_kwargs + ['factor', 'condition', 'indirect']