class Function(function.Function, Signer): from_YASK = True def __new__(cls, *args, **kwargs): if cls in _SymbolCache: newobj = sympy.Function.__new__(cls, *args, **kwargs.get('options', {})) newobj._cached_init() else: # If a Function has no SpaceDimension, than for sure it won't be # used by YASK. We then return a devito.Function, which employs # a standard row-major format for data values indices = cls.__indices_setup__(**kwargs) klass = cls if any(i.is_Space for i in indices) else cls.__base__ newobj = cls.__base__.__new__(klass, *args, **kwargs) return newobj def _allocate_memory(func): """Allocate memory in terms of YASK grids.""" def wrapper(self): if self._data is None: log("Allocating memory for %s%s" % (self.name, self.shape_allocated)) # Fetch the appropriate context context = contexts.fetch(self.dimensions, self.dtype) # Create a YASK grid; this allocates memory grid = context.make_grid(self) # /self._padding/ must be updated as (from the YASK docs): # "The value may be slightly larger [...] due to rounding" padding = [] for i in self.dimensions: if i.is_Space: padding.append((grid.get_left_extra_pad_size(i.name), grid.get_right_extra_pad_size(i.name))) else: # time and misc dimensions padding.append((0, 0)) self._padding = tuple(padding) self._data = Data(grid, self.shape_allocated, self.indices, self.dtype) self._data.reset() return func(self) return wrapper def __del__(self): if self._data is not None: self._data.release_storage() @property @_allocate_memory def _data_buffer(self): ctype = numpy_to_ctypes(self.dtype) cpointer = ctypes.cast(int(self._data.grid.get_raw_storage_buffer()), ctypes.POINTER(ctype)) ndpointer = np.ctypeslib.ndpointer(dtype=self.dtype, shape=self.shape_allocated) casted = ctypes.cast(cpointer, ndpointer) ndarray = np.ctypeslib.as_array(casted, shape=self.shape_allocated) return ndarray @property def data(self): """ The domain data values, as a :class:`Data`. The returned object, which behaves as a :class:`numpy.ndarray`, provides a *view* of the actual data, in row-major format. Internally, the data is stored in whatever layout adopted by YASK. Any read/write from/to the returned :class:`Data` should be performed assuming a row-major storage layout; behind the scenes, these accesses are automatically translated into whatever YASK expects, in order to pick the intended values. Abstracting away the internal storage layout adopted by YASK guarantees that user code works independently of the chosen Devito backend. This may introduce a little performance penalty when accessing data w.r.t. the default Devito backend. Such penalty should however be easily amortizable, as the time spent in running Operators is expected to be vastly greater than any user-level data manipulation. For further information, refer to ``Data.__doc__``. """ return self.data_domain @cached_property @_allocate_memory def data_domain(self): """ .. note:: Alias to ``self.data``. """ return Data(self._data.grid, self.shape, self.indices, self.dtype, offset=self._offset_domain.left) @cached_property @_allocate_memory def data_with_halo(self): return Data(self._data.grid, self.shape_with_halo, self.indices, self.dtype, offset=self._offset_halo.left) @cached_property @_allocate_memory def data_allocated(self): return Data(self._data.grid, self.shape_allocated, self.indices, self.dtype) def _arg_defaults(self, alias=None): args = super(Function, self)._arg_defaults(alias=alias) key = alias or self args[namespace['code-grid-name'](key.name)] = self.data.rawpointer return args def _signature_items(self): return (self.name, ) + tuple(i.name for i in self.indices)
class Function(function.Function): from_YASK = True def _allocate_memory(func): """Allocate memory in terms of YASK grids.""" def wrapper(self): if self._data is None: log("Allocating memory for %s%s" % (self.name, self.shape_allocated)) # Fetch the appropriate context context = contexts.fetch(self.grid, self.dtype) # TODO : the following will fail if not using a SteppingDimension, # eg with save=True one gets /time/ instead /t/ grid = context.make_grid(self) # /self._padding/ must be updated as (from the YASK docs): # "The value may be slightly larger [...] due to rounding" pad = [(0, 0) if i.is_Time else (grid.get_left_extra_pad_size(i.name), grid.get_right_extra_pad_size(i.name)) for i in self.indices] self._padding = pad self._data = Data(grid, self.shape_allocated, self.indices, self.dtype) self._data.reset() return func(self) return wrapper def __del__(self): if self._data is not None: self._data.release_storage() @property @_allocate_memory def _data_buffer(self): ctype = numpy_to_ctypes(self.dtype) cpointer = ctypes.cast(int(self._data.grid.get_raw_storage_buffer()), ctypes.POINTER(ctype)) ndpointer = np.ctypeslib.ndpointer(dtype=self.dtype, shape=self.shape_allocated) casted = ctypes.cast(cpointer, ndpointer) ndarray = np.ctypeslib.as_array(casted, shape=self.shape_allocated) return ndarray @property def data(self): """ The domain data values, as a :class:`Data`. The returned object, which behaves as a :class:`numpy.ndarray`, provides a *view* of the actual data, in row-major format. Internally, the data is stored in whatever layout adopted by YASK. Any read/write from/to the returned :class:`Data` should be performed assuming a row-major storage layout; behind the scenes, these accesses are automatically translated into whatever YASK expects, in order to pick the intended values. Abstracting away the internal storage layout adopted by YASK guarantees that user code works independently of the chosen Devito backend. This may introduce a little performance penalty when accessing data w.r.t. the default Devito backend. Such penalty should however be easily amortizable, as the time spent in running Operators is expected to be vastly greater than any user-level data manipulation. For further information, refer to ``Data.__doc__``. """ return self.data_domain @cached_property @_allocate_memory def data_domain(self): """ .. note:: Alias to ``self.data``. """ return Data(self._data.grid, self.shape, self.indices, self.dtype, offset=self._offset_domain.left) @cached_property @_allocate_memory def data_with_halo(self): return Data(self._data.grid, self.shape_with_halo, self.indices, self.dtype, offset=self._offset_halo.left) @cached_property @_allocate_memory def data_allocated(self): return Data(self._data.grid, self.shape_allocated, self.indices, self.dtype) def initialize(self): raise NotImplementedError def _arg_defaults(self, alias=None): args = super(Function, self)._arg_defaults(alias=alias) key = alias or self args[namespace['code-grid-name'](key.name)] = self.data.rawpointer return args
class Function(dense.Function, Signer): from_YASK = True def __new__(cls, *args, **kwargs): key = cls._cache_key(*args, **kwargs) obj = cls._cache_get(key) if obj is not None: newobj = sympy.Function.__new__(cls, *args, **kwargs.get('options', {})) newobj.__init_cached__(key) return newobj # Not in cache. Create a new Function via core.Function # If a Function has no SpaceDimension, than for sure it won't be # used by YASK. We then return a devito.Function, which employs # a standard row-major format for data values indices = cls.__indices_setup__(**kwargs) klass = cls if any(i.is_Space for i in indices) else cls.__base__ newobj = cls.__base__.__new__(klass, *args, **kwargs) return newobj def __padding_setup__(self, **kwargs): # YASK calculates the padding, so we bypass the dense.Function's autopadding return tuple((0, 0) for i in range(self.ndim)) def _allocate_memory(func): """Allocate memory in terms of YASK vars.""" def wrapper(self): if self._data is None: log("Allocating memory for %s%s" % (self.name, self.shape_allocated)) # Free memory carried by stale symbolic objects # TODO: see issue #944 # CacheManager.clear(dump_contexts=False, force=False) # Fetch the appropriate context context = contexts.fetch(self.dimensions, self.dtype) # Create a YASK var; this allocates memory var = context.make_var(self) # `self._padding` must be updated as (from the YASK docs): # "The value may be slightly larger [...] due to rounding" padding = [] for i in self.dimensions: if i.is_Space: padding.append((var.get_left_extra_pad_size(i.name), var.get_right_extra_pad_size(i.name))) else: # time and misc dimensions padding.append((0, 0)) self._padding = tuple(padding) del self.shape_allocated # Invalidate cached_property self._data = Data(var, self.shape_allocated, self.indices, self.dtype) self._data.reset() return func(self) return wrapper def __del__(self): if self._data is None: # Perhaps data had never been allocated return if self is self.function: # The original Function (e.g., f(x, y)) is in charge of freeing memory, # while this is a no-op for all other objects derived from it (e.g., # (e.g., f(x+1, y), f(x, y-2)) self._data.release_storage() @property @_allocate_memory def _data_buffer(self): num_elements = self._data.var.get_num_storage_elements() shape = self.shape_allocated ctype_1d = dtype_to_ctype(self.dtype) * reduce(mul, shape) if num_elements != reduce(mul, shape): warning("num_storage_elements(%d) != reduce(mul, %s)", num_elements, str(shape)) buf = ctypes.cast(int(self._data.var.get_raw_storage_buffer()), ctypes.POINTER(ctype_1d)).contents return np.frombuffer(buf, dtype=self.dtype).reshape(shape) @property def data(self): """ The domain data values, as a Data. The returned object, which behaves as a `numpy.ndarray`, provides a *view* of the actual data, in row-major format. Internally, the data is stored in whatever layout adopted by YASK. Any read/write from/to the returned Data should be performed assuming a row-major storage layout; behind the scenes, these accesses are automatically translated into whatever YASK expects, in order to pick the intended values. Abstracting away the internal storage layout adopted by YASK guarantees that user code works independently of the chosen Devito backend. This may introduce a little performance penalty when accessing data w.r.t. the default Devito backend. Such penalty should however be easily amortizable, as the time spent in running Operators is expected to be vastly greater than any user-level data manipulation. For further information, refer to ``Data.__doc__``. """ return self.data_domain @cached_property @_allocate_memory def data_domain(self): """ Notes ----- Alias to ``self.data``. """ return Data(self._data.var, self.shape, self.indices, self.dtype, offset=self._offset_domain) @cached_property @_allocate_memory def data_with_halo(self): return Data(self._data.var, self.shape_with_halo, self.indices, self.dtype, offset=self._offset_halo.left) @cached_property @_allocate_memory def _data_allocated(self): return Data(self._data.var, self.shape_allocated, self.indices, self.dtype) def _arg_defaults(self, alias=None): args = super(Function, self)._arg_defaults(alias=alias) key = alias or self args[namespace['code-var-name'](key.name)] = self.data.rawpointer return args def _signature_items(self): return (self.name, ) + tuple(i.name for i in self.indices)
class Function(dense.Function, Signer): from_YASK = True def __new__(cls, *args, **kwargs): if cls in basic._SymbolCache: newobj = sympy.Function.__new__(cls, *args, **kwargs.get('options', {})) newobj._cached_init() else: # If a Function has no SpaceDimension, than for sure it won't be # used by YASK. We then return a devito.Function, which employs # a standard row-major format for data values indices = cls.__indices_setup__(**kwargs) klass = cls if any(i.is_Space for i in indices) else cls.__base__ newobj = cls.__base__.__new__(klass, *args, **kwargs) return newobj def _allocate_memory(func): """Allocate memory in terms of YASK grids.""" def wrapper(self): if self._data is None: log("Allocating memory for %s%s" % (self.name, self.shape_allocated)) # Fetch the appropriate context context = contexts.fetch(self.dimensions, self.dtype) # Create a YASK grid; this allocates memory grid = context.make_grid(self) # `self._padding` must be updated as (from the YASK docs): # "The value may be slightly larger [...] due to rounding" padding = [] for i in self.dimensions: if i.is_Space: padding.append((grid.get_left_extra_pad_size(i.name), grid.get_right_extra_pad_size(i.name))) else: # time and misc dimensions padding.append((0, 0)) self._padding = tuple(padding) del self.shape_allocated # Invalidate cached_property self._data = Data(grid, self.shape_allocated, self.indices, self.dtype) self._data.reset() return func(self) return wrapper def __del__(self): if self._data is not None: self._data.release_storage() @property @_allocate_memory def _data_buffer(self): num_elements = self._data.grid.get_num_storage_elements() shape = self.shape_allocated ctype_1d = dtype_to_ctype(self.dtype) * reduce(mul, shape) if num_elements != reduce(mul, shape): warning("num_storage_elements(%d) != reduce(mul, %s)", num_elements, str(shape)) buf = ctypes.cast( int(self._data.grid.get_raw_storage_buffer()), ctypes.POINTER(ctype_1d)).contents return np.frombuffer(buf, dtype=self.dtype).reshape(shape) @property def data(self): """ The domain data values, as a :class:`Data`. The returned object, which behaves as a :class:`numpy.ndarray`, provides a *view* of the actual data, in row-major format. Internally, the data is stored in whatever layout adopted by YASK. Any read/write from/to the returned :class:`Data` should be performed assuming a row-major storage layout; behind the scenes, these accesses are automatically translated into whatever YASK expects, in order to pick the intended values. Abstracting away the internal storage layout adopted by YASK guarantees that user code works independently of the chosen Devito backend. This may introduce a little performance penalty when accessing data w.r.t. the default Devito backend. Such penalty should however be easily amortizable, as the time spent in running Operators is expected to be vastly greater than any user-level data manipulation. For further information, refer to ``Data.__doc__``. """ return self.data_domain @cached_property @_allocate_memory def data_domain(self): """ Notes ----- Alias to ``self.data``. """ return Data(self._data.grid, self.shape, self.indices, self.dtype, offset=self._offset_domain) @cached_property @_allocate_memory def data_with_halo(self): return Data(self._data.grid, self.shape_with_halo, self.indices, self.dtype, offset=self._offset_halo.left) @cached_property @_allocate_memory def _data_allocated(self): return Data(self._data.grid, self.shape_allocated, self.indices, self.dtype) def _arg_defaults(self, alias=None): args = super(Function, self)._arg_defaults(alias=alias) key = alias or self args[namespace['code-grid-name'](key.name)] = self.data.rawpointer return args def _signature_items(self): return (self.name,) + tuple(i.name for i in self.indices)
class Function(function.Function): from_YASK = True def _allocate_memory(func): """Allocate memory in terms of YASK grids.""" def wrapper(self): if self._data is None: log("Allocating memory for %s (%s)" % (self.name, self.shape)) # Fetch the appropriate context context = contexts.fetch(self.grid, self.dtype) # TODO : the following will fail if not using a SteppingDimension, # eg with save=True one gets /time/ instead /t/ grid = context.make_grid(self) # /self._padding/ must be updated as (from the YASK docs): # "The value may be slightly larger [...] due to rounding" padding = [ 0 if i.is_Time else grid.get_extra_pad_size(i.name) for i in self.indices ] self._padding = tuple((i, ) * 2 for i in padding) self._data = Data(grid, self.shape_allocated, self.indices, self.dtype) self._data.reset() return func(self) return wrapper def __del__(self): if self._data is not None: self._data.release_storage() @property def _data_buffer(self): data = self.data ctype = numpy_to_ctypes(data.dtype) cpointer = ctypes.cast(int(data.grid.get_raw_storage_buffer()), ctypes.POINTER(ctype)) ndpointer = np.ctypeslib.ndpointer(dtype=data.dtype, shape=data.shape) casted = ctypes.cast(cpointer, ndpointer) ndarray = np.ctypeslib.as_array(casted, shape=data.shape) return ndarray @property def shape_with_halo(self): """ Shape of the domain plus the read-only stencil boundary associated with this :class:`Function`. """ # TODO: Drop me after the domain-allocation switch, as this method # will be provided by the superclass return tuple(j + i + k for i, (j, k) in zip(self.shape_domain, self._halo)) @property def shape_allocated(self): """ Shape of the allocated data associated with this :class:`Function`. It includes the domain and halo regions, as well as any additional padding outside of the halo. """ # TODO: Drop me after the domain-allocation switch, as this method # will be provided by the superclass return tuple(j + i + k for i, (j, k) in zip(self.shape_with_halo, self._padding)) @property def data(self): """ The domain data values, as a :class:`Data`. The returned object, which behaves as a :class:`numpy.ndarray`, provides a *view* of the actual data, in row-major format. Internally, the data is stored in whatever layout adopted by YASK. Any read/write from/to the returned :class:`Data` should be performed assuming a row-major storage layout; behind the scenes, these accesses are automatically translated into whatever YASK expects, in order to pick the intended values. Abstracting away the internal storage layout adopted by YASK guarantees that user code works independently of the chosen Devito backend. This may introduce a little performance penalty when accessing data w.r.t. the default Devito backend. Such penalty should however be easily amortizable, as the time spent in running Operators is expected to be vastly greater than any user-level data manipulation. For further information, refer to ``Data.__doc__``. """ return self.data_domain @cached_property @_allocate_memory def data_domain(self): """ .. note:: Alias to ``self.data``. """ return Data(self._data.grid, self.shape, self.indices, self.dtype, offset=self._offset_domain) @cached_property @_allocate_memory def data_with_halo(self): return Data(self._data.grid, self.shape_with_halo, self.indices, self.dtype, offset=self._offset_halo) def initialize(self): raise NotImplementedError