def create_ops_arg(p, name_to_ops_dat, par_to_ops_stencil): if p.is_Constant: return namespace['ops_arg_gbl']( Byref(Constant(name=p.name[1:])), 1, Literal('"%s"' % dtype_to_cstr(p.dtype)), namespace['ops_read']) else: return namespace['ops_arg_dat']( name_to_ops_dat[p.name], 1, par_to_ops_stencil[p], Literal('"%s"' % dtype_to_cstr(p.dtype)), namespace['ops_read'] if p.read_only else namespace['ops_write'])
def create_ops_arg(p, accessible_origin, name_to_ops_dat, par_to_ops_stencil): elements_per_point = 1 dtype = Literal('"%s"' % dtype_to_cstr(p.dtype)) if p.is_Constant: ops_type = namespace['ops_arg_gbl'] ops_name = Byref(Constant(name=p.name[1:])) rw_flag = namespace['ops_read'] else: ops_type = namespace['ops_arg_dat'] accessible_info = accessible_origin[p.name] ops_name = name_to_ops_dat[p.name] \ if accessible_info.time is None \ else name_to_ops_dat[accessible_info.origin_name].\ indexify([Add(accessible_info.time, accessible_info.shift)]) rw_flag = namespace['ops_read'] if p.read_only else namespace[ 'ops_write'] ops_arg = OpsArgDecl(ops_type=ops_type, ops_name=ops_name, elements_per_point=elements_per_point, dtype=dtype, rw_flag=rw_flag) return ops_arg
def __repr__(self): parameters = ",".join([ 'void*' if i.is_Object else dtype_to_cstr(i.dtype) for i in self.parameters ]) return "%s[%s]<%s; %s>" % (self.__class__.__name__, self.name, self.retval, parameters)
def create_ops_arg(p, accessible_origin, name_to_ops_dat, par_to_ops_stencil): if p.is_Constant: return namespace['ops_arg_gbl']( Byref(Constant(name=p.name[1:])), 1, Literal('"%s"' % dtype_to_cstr(p.dtype)), namespace['ops_read']) else: accessible_info = accessible_origin[p.name] dat_name = name_to_ops_dat[p.name] \ if accessible_info.time is None \ else name_to_ops_dat[accessible_info.origin_name].\ indexify([accessible_info.time]) return namespace['ops_arg_dat']( dat_name, 1, par_to_ops_stencil[p], Literal('"%s"' % dtype_to_cstr(p.dtype)), namespace['ops_read'] if p.read_only else namespace['ops_write'])
def get_ops_args(args, stencils, name_to_dat): ops_args = [] for arg in args: if arg.is_Constant: ops_args.append( Call("ops_arg_gbl", [ Byref(Constant(name=arg.name[1:])), 1, String(dtype_to_cstr(arg.dtype)), OPS_READ ], False)) else: ops_args.append( Call("ops_arg_dat", [ name_to_dat[arg.name], 1, stencils[arg.name], String(dtype_to_cstr(arg.dtype)), OPS_WRITE if arg.is_Write else OPS_READ ], False)) return ops_args
def test_create_ops_arg_constant(self): a = Constant(name='*a') ops_arg = create_ops_arg(a, {}, {}, {}) assert ops_arg.ops_type == namespace['ops_arg_gbl'] assert str(ops_arg.ops_name) == str(Byref(Constant(name='a'))) assert ops_arg.elements_per_point == 1 assert ops_arg.dtype == Literal('"%s"' % dtype_to_cstr(a.dtype)) assert ops_arg.rw_flag == namespace['ops_read']
def test_create_ops_arg_constant(self): a = Constant(name='*a') res = create_ops_arg(a, {}, {}, {}) assert type(res) == namespace['ops_arg_gbl'] assert str(res.args[0]) == str(Byref(Constant(name='a'))) assert res.args[1] == 1 assert res.args[2] == Literal('"%s"' % dtype_to_cstr(a.dtype)) assert res.args[3] == namespace['ops_read']
def test_create_ops_arg_constant(self): a = Constant(name='*a') res = create_ops_arg(a, {}, {}) assert res.name == namespace['ops_arg_gbl'].name assert res.args == [ Byref(Constant(name='a')), 1, Literal('"%s"' % dtype_to_cstr(a.dtype)), namespace['ops_read'] ]
def test_create_ops_arg_function(self, read): u = OpsAccessible('u', np.float32, read) dat = OpsDat('u_dat') stencil = OpsStencil('stencil') info = AccessibleInfo(u, None, None) res = create_ops_arg(u, {'u': info}, {'u': dat}, {u: stencil}) assert type(res) == namespace['ops_arg_dat'] assert res.args == (dat, 1, stencil, Literal('"%s"' % dtype_to_cstr(u.dtype)), namespace['ops_read'] if read else namespace['ops_write'])
def test_create_ops_arg_function(self, read): u = OpsAccessible('u', dtype=np.float32, read_only=read) dat = OpsDat('u_dat') stencil = OpsStencil('stencil') info = AccessibleInfo(u, None, None) ops_arg = create_ops_arg(u, {'u': info}, {'u': dat}, {u: stencil}) assert ops_arg.ops_type == namespace['ops_arg_dat'] assert ops_arg.ops_name == OpsDat('u_dat') assert ops_arg.elements_per_point == 1 assert ops_arg.dtype == Literal('"%s"' % dtype_to_cstr(u.dtype)) assert ops_arg.rw_flag == \ namespace['ops_read'] if read else namespace['ops_write']
def _C_typename(self): if isinstance(self.dtype, str): return self.dtype return dtype_to_cstr(self.dtype)
def _C_typedata(self): return dtype_to_cstr(self.dtype)
def visit_LocalExpression(self, o): return c.Initializer( c.Value(dtype_to_cstr(o.dtype), ccode(o.expr.lhs, dtype=o.dtype)), ccode(o.expr.rhs, dtype=o.dtype))
class Dimension(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. Dimension is the root of a hierarchy of classes, which looks as follows (only the classes exposed to the level of the user API are shown):: Dimension | --------------------------- | | BasicDimension DefaultDimension | DerivedDimension | --------------------------------------- | | | SteppingDimension SubDimension ConditionalDimension 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_Custom = False is_Derived = False is_NonlinearDerived = False is_Sub = False is_Conditional = False is_Stepping = False is_Modulo = False is_Incr = False is_Shifted = False _C_typename = 'const %s' % dtype_to_cstr(np.int32) _C_typedata = _C_typename def __new__(cls, *args, **kwargs): """ Equivalent to ``BasicDimension(*args, **kwargs)``. Notes ----- This is only necessary for backwards compatibility, as originally there was no BasicDimension (i.e., Dimension was just the top class). """ if cls is Dimension: return BasicDimension(*args, **kwargs) else: return BasicDimension.__new__(cls, *args, **kwargs) @classmethod def __dtype_setup__(cls, **kwargs): # Unlike other Symbols, Dimensions can only be integers return np.int32 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) @property def symbolic_incr(self): """The increment value while iterating over the Dimension.""" return sympy.S.One @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 is_const(self): return False @property def root(self): return self @property def _maybe_distributed(self): """Could it be a distributed Dimension?""" return True @property def _C_name(self): return self.name @cached_property def _defines(self): return frozenset({self}) @cached_property def _defines_symbols(self): candidates = [ self.symbolic_min, self.symbolic_max, self.symbolic_size, self.symbolic_incr ] return frozenset(i for i in candidates if not i.is_Number) @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) @memoized_meth 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 performed. 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 Used for spacing overriding and 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 args = {self.min_name: loc_minv, self.max_name: loc_maxv} # Maybe override spacing if grid is not None: try: spacing_map = {k.name: v for k, v in grid.spacing_map.items()} args[self.spacing.name] = spacing_map[self.spacing.name] except KeyError: pass except AttributeError: # See issue #1524 warning("Unable to override spacing") return args 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: if is_integer(interval.upper): upper = interval.upper else: # Autopadding causes non-integer upper limit upper = interval.upper.subs(args) if args[self.max_name] + 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 %s=%d < %s=%d" % (self.max_name, args[self.max_name], self.min_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__
def _C_typedata(self): return dtype_to_cstr(self.dtype)
def _C_typename(self): return '%s%s' % ('const ' if self._is_const else '', dtype_to_cstr(self.dtype))
def _C_typedata(self): return 'ACC<%s>' % dtype_to_cstr(self.dtype)
def _C_typename(self): return '%sACC<%s> &' % ( 'const ' if self.read_only else '', dtype_to_cstr(self.dtype) )
def visit_LocalExpression(self, o): return c.Initializer(c.Value(dtype_to_cstr(o.dtype), ccode(o.expr.lhs, dtype=o.dtype)), ccode(o.expr.rhs, dtype=o.dtype))
def _C_typedata(self): return 'volatile %s' % dtype_to_cstr(self.dtype)
def _C_typename(self): return '%s%s' % ('const ' if self.is_const else '', dtype_to_cstr(self.dtype))
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__