Skip to content

Commit

Permalink
Merge pull request #345 from opesci/int_start_end
Browse files Browse the repository at this point in the history
Introduce start and end arguments for dimensions
  • Loading branch information
navjotk authored Oct 24, 2017
2 parents 7ff22da + 96da13a commit 6e8a390
Show file tree
Hide file tree
Showing 15 changed files with 347 additions and 194 deletions.
104 changes: 70 additions & 34 deletions devito/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import numpy as np
from cached_property import cached_property
from collections import namedtuple

from devito.exceptions import InvalidArgument
from devito.logger import debug
Expand Down Expand Up @@ -34,7 +35,7 @@ class Argument(object):
def __init__(self, name, provider, default_value=None):
self.name = name
self.provider = provider
self._value = self._default_value = default_value
self._value = self.default_value = default_value

@property
def value(self):
Expand All @@ -48,38 +49,39 @@ def value(self):
except AttributeError:
return self._value

@property
def ready(self):
return self._value is not None

@property
def dtype(self):
return self.provider.dtype

def reset(self):
self._value = self._default_value
self._value = self.default_value

@abc.abstractproperty
def verify(self, kwargs):
return

def __repr__(self):
return self.name


class ScalarArgument(Argument):

""" Class representing scalar arguments that a kernel might expect.
Most commonly used to pass dimension sizes
enforce determines whether any reduction will be performed or not.
i.e. if it is a user-provided value, use it directly.
"""

is_ScalarArgument = True

def __init__(self, name, provider, reducer, default_value=None):
def __init__(self, name, provider, reducer=lambda old, new: new, default_value=None):
super(ScalarArgument, self).__init__(name, provider, default_value)
self.reducer = reducer

def verify(self, value):
def verify(self, value, enforce=False):
# Assuming self._value was initialised as appropriate for the reducer
if value is not None:
if self._value is not None:
if self._value is not None and not enforce:
self._value = self.reducer(self._value, value)
else:
self._value = value
Expand All @@ -101,10 +103,10 @@ def verify(self, value):
if value is None:
value = self._value

verify = self.provider.shape == value.shape
verify = len(self.provider.shape) == len(value.shape)

verify = verify and all(d.verify(v) for d, v in zip(self.provider.indices,
value.shape))
verify = verify and all(d.verify(v) for d, v in
zip(self.provider.indices, value.shape))
if verify:
self._value = value

Expand Down Expand Up @@ -157,14 +159,18 @@ class DimensionArgProvider(ArgumentProvider):

def __init__(self, *args, **kwargs):
super(DimensionArgProvider, self).__init__(*args, **kwargs)
self._value = self._default_value

def reset(self):
self._value = self._default_value
for i in self.rtargs:
i.reset()

@property
def value(self):
return self._value
""" Returns a tuple (same order as rtargs) with the current value of each rtarg
If any of the rtargs has value None, the return value here is None.
"""
child_values = tuple([i.value for i in self.rtargs])
return child_values if all(i is not None for i in child_values) else None

@property
def dtype(self):
Expand All @@ -173,11 +179,42 @@ def dtype(self):

@cached_property
def rtargs(self):
size = ScalarArgument("%s_size" % self.name, self, max)
return [size]
size = ScalarArgument(self.size_name, self, max)
start = ScalarArgument(self.start_name, self, max, 0)
end = ScalarArgument(self.end_name, self, max)
return namedtuple("RuntimeArguments", ["size", "start", "end"])(size, start, end)

def _promote(self, value):
""" Strictly, a dimension's value is a 3-tuple consisting of the
values of each of its rtargs - currently size, start and end. However, for
convenience, we may accept partial representations of the value, e.g. scalars
and 2-tuples and interpret them in a certain way while assuming defaults for
missing information. If value is:
3-tuple: it contains explicit values for all 3 rtargs and hence will be used
directly
2-tuple: We assume we are being provided the (start, end) values. This will be
promoted to a 3-tuple assuming size to be the same as end.
scalar: We assume we are being provided the value of size. Promote to 3-tuple
by assuming this scalar is the size and the end of the dimension. start will
default to 0.
"""

if not isinstance(value, tuple):
# scalar
size, start, end = self.rtargs
value = (value, start.default_value, value)
else:
if len(value) == 2:
# 2-tuple
# Assume we've been passed a (start, end) tuple
start, end = value
value = (end, start, end)
elif len(value) != 3:
raise InvalidArgument("Expected either a scalar value or a tuple(2/3)")
return value

# TODO: Can we do without a verify on a dimension?
def verify(self, value):
def verify(self, value, enforce=False):
verify = True
if value is None:
if self.value is not None:
Expand All @@ -189,37 +226,36 @@ def verify(self, value):
return False
except AttributeError:
return False

try:
# Make sure we're dealing with a 3-tuple. See docstring of _promote for more
value = self._promote(value)
if hasattr(self, 'parent'):
parent_value = self.parent.value
if parent_value is not None:
value = self.reducer(value, parent_value)
if parent_value is not None and not enforce:
parent_value = self._promote(parent_value)
value = tuple([self.reducer(i1, i2) for i1, i2 in zip(value,
parent_value)])
verify = verify and self.parent.verify(value)
except AttributeError:
pass

if value == self.value:
return True

# Derived dimensions could be linked through constraints
# At this point, a constraint needs to be added that enforces
# dim_e - dim_s < SOME_MAX
# Also need a default constraint that dim_e > dim_s (or vice-versa)
verify = verify and all([a.verify(v) for a, v in zip(self.rtargs, (value,))])
if verify:
self._value = value
verify = verify and all([a.verify(v, enforce=enforce) for a, v in
zip(self.rtargs, value)])
return verify


class ConstantArgProvider(ArgumentProvider):

""" Class used to decorate Constat Data objects with behaviour required for runtime
""" Class used to decorate Constant Data objects with behaviour required for runtime
arguments.
"""

@cached_property
def rtargs(self):
return [ScalarArgument(self.name, self, lambda old, new: new, self.data)]
return (ScalarArgument(self.name, self, default_value=self.data),)


class TensorFunctionArgProvider(ArgumentProvider):
Expand All @@ -230,7 +266,7 @@ class TensorFunctionArgProvider(ArgumentProvider):

@cached_property
def rtargs(self):
return [TensorArgument(self.name, self)]
return (TensorArgument(self.name, self),)


class ScalarArgProvider(ArgumentProvider):
Expand All @@ -241,7 +277,7 @@ class ScalarArgProvider(ArgumentProvider):

@cached_property
def rtargs(self):
return [ScalarArgument(self.name, self, self.dtype)]
return (ScalarArgument(self.name, self, self.dtype),)


class ArrayArgProvider(ArgumentProvider):
Expand All @@ -252,7 +288,7 @@ class ArrayArgProvider(ArgumentProvider):

@cached_property
def rtargs(self):
return [TensorArgument(self.name, self)]
return (TensorArgument(self.name, self),)


class ObjectArgProvider(ArgumentProvider):
Expand All @@ -262,7 +298,7 @@ class ObjectArgProvider(ArgumentProvider):

@cached_property
def rtargs(self):
return [PtrArgument(self.name, self)]
return (PtrArgument(self.name, self),)


def log_args(arguments):
Expand Down
3 changes: 2 additions & 1 deletion devito/core/autotuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def autotune(operator, arguments, tunable):
timesteps = 1
elif len(sequentials) == 1:
sequential = sequentials[0]
timesteps = sequential.extent(finish=options['at_squeezer'])
start = sequential.dim.rtargs.start.default_value
timesteps = sequential.extent(start=start, finish=options['at_squeezer'])
if timesteps < 0:
timesteps = options['at_squeezer'] - timesteps + 1
info_at("Adjusted auto-tuning timestep to %d" % timesteps)
Expand Down
34 changes: 31 additions & 3 deletions devito/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,39 @@ def __str__(self):
@cached_property
def symbolic_size(self):
"""The symbolic size of this dimension."""
return Symbol(name=self.rtargs[0].name)
return Symbol(name=self.size_name)

@cached_property
def symbolic_start(self):
return Symbol(name=self.start_name)

@cached_property
def symbolic_end(self):
return Symbol(name=self.end_name)

@property
def size(self):
return None
def symbolic_extent(self):
"""Return the extent of the loop over this dimension.
Would be the same as size if using default values """
_, start, end = self.rtargs
return (self.symbolic_end - self.symbolic_start)

@property
def limits(self):
_, start, end = self.rtargs
return (self.symbolic_start, self.symbolic_end, 1)

@property
def size_name(self):
return "%s_size" % self.name

@property
def start_name(self):
return "%s_s" % self.name

@property
def end_name(self):
return "%s_e" % self.name


class SpaceDimension(Dimension):
Expand Down
6 changes: 3 additions & 3 deletions devito/dle/backends/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ def _loop_blocking(self, state, **kwargs):
# Build Iteration over blocks
dim = blocked.setdefault(i, Dimension("%s_block" % i.dim.name))
block_size = dim.symbolic_size
iter_size = i.dim.symbolic_size
iter_size = i.dim.symbolic_extent
start = i.limits[0] - i.offsets[0]
finish = iter_size - i.offsets[1]
finish = i.dim.symbolic_end - i.offsets[1]
innersize = iter_size - (-i.offsets[0] + i.offsets[1])
finish = finish - (innersize % block_size)
inter_block = Iteration([], dim, [start, finish, block_size],
Expand All @@ -182,7 +182,7 @@ def _loop_blocking(self, state, **kwargs):
# This will be used for remainder loops, executed when any
# dimension size is not a multiple of the block size.
start = inter_block.limits[1]
finish = iter_size - i.offsets[1]
finish = i.dim.symbolic_end - i.offsets[1]
remainder = i._rebuild([], limits=[start, finish, 1], offsets=None)
remainders.append(remainder)

Expand Down
2 changes: 1 addition & 1 deletion devito/dse/backends/advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _eliminate_inter_stencil_redundancies(self, cluster, template, **kwargs):
time_invariants = {v.rhs: g.time_invariant(v) for v in g.values()}

# Template for captured redundancies
shape = tuple(i.symbolic_size for i in indices)
shape = tuple(i.symbolic_extent for i in indices)
make = lambda i: Array(name=template(i), shape=shape,
dimensions=indices).indexed

Expand Down
20 changes: 9 additions & 11 deletions devito/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from devito.dse import as_symbol, retrieve_terminals
from devito.types import Indexed, Symbol
from devito.stencil import Stencil
from devito.tools import as_tuple, filter_ordered, flatten, is_integer
from devito.tools import as_tuple, filter_ordered, flatten
from devito.arguments import ArgumentProvider, Argument

__all__ = ['Node', 'Block', 'Denormals', 'Expression', 'Callable', 'Call',
Expand Down Expand Up @@ -395,16 +395,14 @@ def bounds(self, start=None, finish=None):
available (either statically known or provided through ``start``/
``finish``). ``None`` is used as a placeholder in the returned 2-tuple
if a limit is unknown."""
try:
lower = int(self.limits[0]) - self.offsets[0]
except (TypeError, ValueError):
if is_integer(start):
lower = start - self.offsets[0]
try:
upper = int(self.limits[1]) - self.offsets[1]
except (TypeError, ValueError):
if is_integer(finish):
upper = finish - self.offsets[1]
lower = start if start is not None else self.limits[0]
upper = finish if finish is not None else self.limits[1]
if lower and self.offsets[0]:
lower = lower - self.offsets[0]

if upper and self.offsets[1]:
upper = upper - self.offsets[1]

return (lower, upper)

def extent(self, start=None, finish=None):
Expand Down
Loading

0 comments on commit 6e8a390

Please sign in to comment.