def test_flow_detection(self): """Test detection of information flow.""" grid = Grid((10, 10)) u2 = TimeFunction(name="u2", grid=grid, time_order=2) u1 = TimeFunction(name="u1", grid=grid, save=10, time_order=2) exprs = [LoweredEq(indexify(Eq(u1.forward, u1 + 2.0 - u1.backward))), LoweredEq(indexify(Eq(u2.forward, u2 + 2*u2.backward - u1.dt2)))] mapper = detect_flow_directions(exprs) assert mapper.get(grid.stepping_dim) == {Forward} assert mapper.get(grid.time_dim) == {Any, Forward} assert all(mapper.get(i) == {Any} for i in grid.dimensions)
def test_multiple_eqs(self, exprs, expected, ti0, ti1, ti3, fa): """ Tests data dependences across ordered sequences of equations representing a scope. ``expected`` is a list of comma-separated words, each word representing a dependence in the scope and consisting of three pieces of information: * the name of the function inducing a dependence * if it's a flow, anti, or output dependence * the dimension causing the dependence """ exprs = [ LoweredEq(i) for i in EVAL(exprs, ti0.base, ti1.base, ti3.base, fa) ] expected = [tuple(i.split(',')) for i in expected] # Force innatural flow, only to stress the compiler to see if it was # capable of detecting anti-dependences for i in exprs: i.ispace._directions = {i: Forward for i in i.ispace.directions} scope = Scope(exprs) assert len(scope.d_all) == len(expected) for i in ['flow', 'anti', 'output']: for dep in getattr(scope, 'd_%s' % i): item = (dep.function.name, i, str(dep.cause)) assert item in expected expected.remove(item) # Sanity check: we did find all of the expected dependences assert len(expected) == 0
def _lower_exprs(cls, expressions, **kwargs): """ Expression lowering: * Form and gather any required implicit expressions; * Apply rewrite rules; * Evaluate derivatives; * Flatten vectorial equations; * Indexify Functions; * Apply substitution rules; * Shift indices for domain alignment. """ # Add in implicit expressions expressions = generate_implicit_exprs(expressions) # Specialization is performed on unevaluated expressions expressions = cls._specialize_dsl(expressions, **kwargs) # Lower functional DSL expressions = flatten([i.evaluate for i in expressions]) expressions = [j for i in expressions for j in i._flatten] # A second round of specialization is performed on nevaluated expressions expressions = cls._specialize_exprs(expressions, **kwargs) # "True" lowering (indexification, shifting, ...) expressions = lower_exprs(expressions, **kwargs) processed = [LoweredEq(i) for i in expressions] return processed
def test_single_eq(self, expr, expected, ti0, ti1, fa): """ Tests data dependences within a single equation consisting of only two Indexeds. ``expected`` is a comma-separated word consisting of four pieces of information: * if it's a flow, anti, or output dependence * if it's loop-carried or loop-independent * the dimension causing the dependence * whether it's direct or indirect (i.e., through A[B[i]]) """ expr = LoweredEq(EVAL(expr, ti0.base, ti1.base, fa)) # Force innatural flow, only to stress the compiler to see if it was # capable of detecting anti-dependences expr.ispace._directions = {i: Forward for i in expr.ispace.directions} scope = Scope(expr) deps = scope.d_all if expected is None: assert len(deps) == 0 return else: type, mode, exp_cause, regular = expected.split(',') if type == 'all': assert len(deps) == 2 else: assert len(deps) == 1 dep = deps[0] # Check type types = ['flow', 'anti'] if type != 'all': types.remove(type) assert len(getattr(scope, 'd_%s' % type)) == 1 assert all(len(getattr(scope, 'd_%s' % i)) == 0 for i in types) else: assert all(len(getattr(scope, 'd_%s' % i)) == 1 for i in types) # Check mode assert getattr(dep, 'is_%s' % mode)() # Check cause if exp_cause == 'None': assert not dep.cause return else: assert len(dep.cause) == 1 cause = dep.cause.pop() assert cause.name == exp_cause # Check mode restricted to the cause assert getattr(dep, 'is_%s' % mode)(cause) non_causes = [i for i in [x, y, z] if i is not cause] assert all(not getattr(dep, 'is_%s' % mode)(i) for i in non_causes) # Check if it's regular or irregular assert getattr(dep.source, 'is_%s' % regular) or\ getattr(dep.sink, 'is_%s' % regular)
def _specialize_exprs(self, expressions): expressions = super(Operator, self)._specialize_exprs(expressions) # No matter whether offloading will occur or not, all YASK grids accept # negative indices when using the get/set_element_* methods (up to the # padding extent), so the OOB-relative data space should be adjusted return [LoweredEq(e, e.ispace, e.dspace.zero([d for d in e.dimensions if d.is_Space]), e.reads, e.writes) for e in expressions]
def _specialize_exprs(self, expressions): # Align data accesses to the computational domain if not a yask.Function key = lambda i: i.is_DiscreteFunction and not i.from_YASK expressions = [align_accesses(e, key=key) for e in expressions] expressions = super(OperatorYASK, self)._specialize_exprs(expressions) # No matter whether offloading will occur or not, all YASK vars accept # negative indices when using the get/set_element_* methods (up to the # padding extent), so the OOB-relative data space should be adjusted return [LoweredEq(e, dspace=e.dspace.zero([d for d in e.dimensions if d.is_Space])) for e in expressions]
def test_lower_func_as_ind(self): grid = Grid((11, 11)) x, y = grid.dimensions t = grid.stepping_dim h = DefaultDimension("h", default_value=10) u = TimeFunction(name='u', grid=grid, time_order=2, space_order=2) oh = Function(name="ou", dimensions=(h, ), shape=(10, ), dtype=int) eq = [Eq(u.forward, u._subs(x, x + oh))] lowered = LoweredEq( Eq(u[t + 1, x + 2, y + 2], u[t, x + oh[h] + 2, y + 2])) with timed_region('x'): leq = Operator._lower_exprs(eq) assert leq[0] == lowered
def test_loops_ompized(fa, fb, fc, fd, t0, t1, t2, t3, exprs, expected, iters): scope = [fa, fb, fc, fd, t0, t1, t2, t3] node_exprs = [Expression(LoweredEq(EVAL(i, *scope))) for i in exprs] ast = iters[6](iters[7](node_exprs)) ast = iet_analyze(ast) nodes = transform(ast, mode='openmp').nodes iterations = FindNodes(Iteration).visit(nodes) assert len(iterations) == len(expected) # Check for presence of pragma omp for i, j in zip(iterations, expected): pragmas = i.pragmas if j is True: assert len(pragmas) == 1 pragma = pragmas[0] assert 'omp for' in pragma.value else: for k in pragmas: assert 'omp for' not in k.value
def _specialize_exprs(self, expressions): """Transform ``expressions`` into a backend-specific representation.""" return [LoweredEq(i) for i in expressions]
def __init__(self, expressions, **kwargs): expressions = as_tuple(expressions) # Input check if any(not isinstance(i, sympy.Eq) for i in expressions): raise InvalidOperator("Only SymPy expressions are allowed.") self.name = kwargs.get("name", "Kernel") subs = kwargs.get("subs", {}) time_axis = kwargs.get("time_axis", Forward) dse = kwargs.get("dse", configuration['dse']) dle = kwargs.get("dle", configuration['dle']) # Header files, etc. self._headers = list(self._default_headers) self._includes = list(self._default_includes) self._globals = list(self._default_globals) # Required for compilation self._compiler = configuration['compiler'] self._lib = None self._cfunction = None # References to local or external routines self.func_table = OrderedDict() # Expression lowering and analysis expressions = [LoweredEq(e, subs=subs) for e in expressions] self.dtype = retrieve_dtype(expressions) self.input, self.output, self.dimensions = retrieve_symbols( expressions) # Set the direction of time acoording to the given TimeAxis for time in [d for d in self.dimensions if d.is_Time]: if not time.is_Stepping: time.reverse = time_axis == Backward # Parameters of the Operator (Dimensions necessary for data casts) parameters = self.input + self.dimensions # Group expressions based on their iteration space and data dependences, # and apply the Devito Symbolic Engine (DSE) for flop optimization clusters = clusterize(expressions) clusters = rewrite(clusters, mode=set_dse_mode(dse)) # Lower Clusters to an Iteration/Expression tree (IET) nodes = iet_build(clusters, self.dtype) # Introduce C-level profiling infrastructure nodes, self.profiler = self._profile_sections(nodes, parameters) # Translate into backend-specific representation (e.g., GPU, Yask) nodes = self._specialize(nodes, parameters) # Apply the Devito Loop Engine (DLE) for loop optimization dle_state = transform(nodes, *set_dle_mode(dle)) # Update the Operator state based on the DLE self.dle_arguments = dle_state.arguments self.dle_flags = dle_state.flags self.func_table.update( OrderedDict([(i.name, MetaCall(i, True)) for i in dle_state.elemental_functions])) parameters.extend([i.argument for i in self.dle_arguments]) self.dimensions.extend([ i.argument for i in self.dle_arguments if isinstance(i.argument, Dimension) ]) self._includes.extend(list(dle_state.includes)) # Introduce the required symbol declarations nodes = iet_insert_C_decls(dle_state.nodes, self.func_table) # Initialise ArgumentEngine self.argument_engine = ArgumentEngine(clusters.ispace, parameters, self.dle_arguments) parameters = self.argument_engine.arguments # Finish instantiation super(Operator, self).__init__(self.name, nodes, 'int', parameters, ())
def _specialize_exprs(cls, expressions): """ Backend hook for specialization at the Expression level. """ return [LoweredEq(i) for i in expressions]
def __init__(self, expressions, **kwargs): expressions = as_tuple(expressions) # Input check if any(not isinstance(i, sympy.Eq) for i in expressions): raise InvalidOperator("Only SymPy expressions are allowed.") self.name = kwargs.get("name", "Kernel") subs = kwargs.get("subs", {}) dse = kwargs.get("dse", configuration['dse']) dle = kwargs.get("dle", configuration['dle']) # Header files, etc. self._headers = list(self._default_headers) self._includes = list(self._default_includes) self._globals = list(self._default_globals) # Required for compilation self._compiler = configuration['compiler'] self._lib = None self._cfunction = None # References to local or external routines self.func_table = OrderedDict() # Expression lowering and analysis expressions = [LoweredEq(e, subs=subs) for e in expressions] self.dtype = retrieve_dtype(expressions) self.input = filter_sorted(flatten(e.reads for e in expressions)) self.output = filter_sorted(flatten(e.writes for e in expressions)) self.dimensions = filter_sorted( flatten(e.dimensions for e in expressions)) # Group expressions based on their iteration space and data dependences, # and apply the Devito Symbolic Engine (DSE) for flop optimization clusters = clusterize(expressions) clusters = rewrite(clusters, mode=set_dse_mode(dse)) # Lower Clusters to an Iteration/Expression tree (IET) nodes = iet_build(clusters, self.dtype) # Introduce C-level profiling infrastructure nodes, self.profiler = self._profile_sections(nodes) # Translate into backend-specific representation (e.g., GPU, Yask) nodes = self._specialize(nodes) # Apply the Devito Loop Engine (DLE) for loop optimization dle_state = transform(nodes, *set_dle_mode(dle)) # Update the Operator state based on the DLE self.dle_arguments = dle_state.arguments self.dle_flags = dle_state.flags self.func_table.update( OrderedDict([(i.name, MetaCall(i, True)) for i in dle_state.elemental_functions])) self.dimensions.extend([ i.argument for i in self.dle_arguments if isinstance(i.argument, Dimension) ]) self._includes.extend(list(dle_state.includes)) # Introduce the required symbol declarations nodes = iet_insert_C_decls(dle_state.nodes, self.func_table) # Insert data and pointer casts for array parameters and profiling structs nodes = self._build_casts(nodes) # Derive parameters as symbols not defined in the kernel itself parameters = self._build_parameters(nodes) # Finish instantiation super(Operator, self).__init__(self.name, nodes, 'int', parameters, ())