From a329e5805a6730259fa8617520d00f61b477e146 Mon Sep 17 00:00:00 2001 From: Joost van Zwieten Date: Thu, 18 Apr 2024 23:46:11 +0200 Subject: [PATCH] remove unused Evaluable.evalfs,serialized etc. With `Evaluable.eval` rewritten using `evaluable.compile`, many evaluable methods and properties are now unused. This patch removes them. --- nutils/evaluable.py | 404 +--------------------------------------- tests/test_evaluable.py | 86 --------- 2 files changed, 1 insertion(+), 489 deletions(-) diff --git a/nutils/evaluable.py b/nutils/evaluable.py index 4fd94520f..d6e302b90 100644 --- a/nutils/evaluable.py +++ b/nutils/evaluable.py @@ -145,20 +145,6 @@ def __init__(self, args: typing.Tuple['Evaluable', ...]): def evalf(*args): raise NotImplementedError('Evaluable derivatives should implement the evalf method') - def evalf_withtimes(self, times, *args): - with times[self]: - return self.evalf(*args) - - @cached_property - def dependencies(self): - '''collection of all function arguments''' - deps = {} - for func in self.__args: - funcdeps = func.dependencies - deps.update(funcdeps) - deps[func] = len(funcdeps) - return types.frozendict(deps) - @cached_property def arguments(self): 'a frozenset of all arguments of this evaluable' @@ -168,41 +154,6 @@ def arguments(self): def isconstant(self): return not self.arguments - @cached_property - def ordereddeps(self): - '''collection of all function arguments such that the arguments to - dependencies[i] can be found in dependencies[:i]''' - deps = self.dependencies.copy() - deps.pop(EVALARGS, None) - return tuple([EVALARGS] + sorted(deps, key=deps.__getitem__)) - - @cached_property - def dependencytree(self): - '''lookup table of function arguments into ordereddeps, such that - ordereddeps[i].__args[j] == ordereddeps[dependencytree[i][j]], and - self.__args[j] == ordereddeps[dependencytree[-1][j]]''' - args = self.ordereddeps - return tuple(tuple(map(args.index, func.__args)) for func in args+(self,)) - - @property - def serialized(self): - return zip(self.ordereddeps[1:]+(self,), self.dependencytree[1:]) - - # This property is a derivation of `ordereddeps[1:]` where the `Evaluable` - # instances are mapped to the `evalf` methods of the instances. Asserting - # that functions are immutable is difficult and currently - # `types._isimmutable` marks all functions as mutable. Since the - # `types.CacheMeta` machinery asserts immutability of the property, we have - # to resort to a regular `functools.cached_property`. Nevertheless, this - # property should be treated as if it is immutable. - @cached_property - def _serialized_evalf_head(self): - return tuple(op.evalf for op in self.ordereddeps[1:]) - - @property - def _serialized_evalf(self): - return zip(itertools.chain(self._serialized_evalf_head, (self.evalf,)), self.dependencytree[1:]) - def _node(self, cache, subgraph, times, unique_loop_ids): if self in cache: return cache[self] @@ -228,68 +179,6 @@ def eval(self, **evalargs): return compile(self, simplify=False, stats=False, cache_const_intermediates=False)(**evalargs) - def eval_withtimes(self, times, **evalargs): - '''Evaluate function on a specified element, point set while measure time of each step.''' - - values = [evalargs] - try: - values.extend(op.evalf_withtimes(times, *[values[i] for i in indices]) for op, indices in self.serialized) - except KeyboardInterrupt: - raise - except Exception as e: - log.error(self._format_stack(values, e)) - raise - else: - return values[-1] - - @contextlib.contextmanager - def session(self, graphviz): - if graphviz is None: - yield self.eval - return - stats = collections.defaultdict(_Stats) - - def eval(**args): - return self.eval_withtimes(stats, **args) - with log.context('eval'): - yield eval - node = self._node({}, None, stats, False) - maxtime = builtins.max(n.metadata[1].time for n in node.walk(set())) - tottime = builtins.sum(n.metadata[1].time for n in node.walk(set())) - aggstats = tuple((key, builtins.sum(v.time for v in values), builtins.sum(v.ncalls for v in values)) for key, values in util.gather(n.metadata for n in node.walk(set()))) - fill_color = (lambda node: '0,{:.2f},1'.format(node.metadata[1].time/maxtime)) if maxtime else None - node.export_graphviz(fill_color=fill_color, dot_path=graphviz) - log.info('total time: {:.0f}ms\n'.format(tottime/1e6) + '\n'.join('{:4.0f} {} ({} calls, avg {:.3f} per call)'.format(t / 1e6, k, n, t / (1e6*n)) - for k, t, n in sorted(aggstats, reverse=True, key=lambda item: item[1]) if n)) - - def _iter_stack(self): - yield '%0 = EVALARGS' - for i, (op, indices) in enumerate(self.serialized, start=1): - s = [f'%{i} = {op}'] - if indices: - args = [f'%{i}' for i in indices] - try: - sig = inspect.signature(op.evalf) - except ValueError: - pass - else: - for i, param in enumerate(sig.parameters.values()): - if i < len(args) and param.kind == param.POSITIONAL_OR_KEYWORD: - args[i] = param.name + '=' + args[i] - s.extend(args) - yield ' '.join(s) - - def _format_stack(self, values, e): - lines = [f'evaluation failed in step {len(values)}/{len(self.dependencies)+1}'] - stack = self._iter_stack() - for v, op in zip(values, stack): # NOTE values must come first to avoid popping next item from stack - s = f'{type(v).__name__}' - if numeric.isarray(v): - s += f'<{v.dtype.kind}:{",".join(str(n) for n in v.shape)}>' - lines.append(f'{op} --> {s}') - lines.append(f'{next(stack)} --> {e}') - return '\n '.join(lines) - @util.deep_replace_property def simplified(obj): retval = obj._simplified() @@ -325,77 +214,6 @@ def _loops(self): deps |= arg._loops return deps.view() - @cached_property - def _loop_deps(self): - deps = util.IDSet() - for arg in self.__args: - deps |= arg._loop_deps - return deps.view() - - def _combine_loops(self, candidates=None): - if candidates is None: - candidates = self._loop_deps - candidates = candidates.copy() - - while candidates: - # Select the loops from candidates that can be combined. Given an - # index, we select all loops that have that index and are not a - # dependency of another loop in `candidates`. The latter ensures - # that the `candidates` do not disappear from `self` other than the - # deliberate combining performed here. To illustrate this, consider - # the following abstract loop structure - # - # A: Add - # B: LoopSum, loop_index=i - # C: Add - # D: LoopSum, loop_index=j - # ..., does not depend on i - # E: LoopSum, loop_index=j - # ..., does not depend on i - # F: LoopSum, loop_index=i - # ... - # - # Loops D and E are invariant loops of B. The default candidates of - # A are B, D, E and F. By combing D and E first, B would be replaced by B', - # - # A: Add - # B': LoopSum, loop_index=i - # C': Add - # D': ArrayFromTuple, index=0 - # DE': LoopTuple(D, E), loop_index=j - # E': ArrayFromTuple, index=0 - # DE' - # F: LoopSum, loop_index=i - # ... - # - # which is not in the set of candidates and we'd miss the - # opportunity to combine B' with F. Or worse: we combine B with F, - # ignore the former (`ArrayFromTuple`) *and* keep B'. - loops = [] - for loop in candidates: - if loops and loop.index != loops[0].index: - continue # take one index at a time - if all(loop not in other._loop_deps for other in candidates if other is not loop): - loops.append(loop) - candidates.difference_update(loops) - index = loops[0].index - - replacements = util.IDDict() - if len(loops) >= 2: - combined = _LoopTuple(tuple(loops), index.loop_id, index.length) - combined = combined._combine_loops(combined.body_arg._loop_deps - combined._loop_deps) - for i, loop in enumerate(loops): - replacements[loop] = ArrayFromTuple(combined, i, loop.shape, loop.dtype) - else: - loop, = loops - combined = loop._combine_loops(loop.body_arg._loop_deps - loop._loop_deps) - if combined != loop: - replacements[loop] = combined - if replacements: - self = util.shallow_replace(replacements.get, self) - - return self - @cached_property def _compile_block_id(self): # The id of the block where this `Evaluable` must be evaluated. See @@ -445,27 +263,12 @@ def _compile_expression(self, py_self: _pyast.Variable, *args: _pyast.Expression return py_self.get_attr('evalf').call(*args) -class EVALARGS(Evaluable): - def __init__(self): - super().__init__(args=()) - - def _node(self, cache, subgraph, times, unique_loop_ids): - return InvisibleNode((type(self).__name__, _Stats())) - - -EVALARGS = EVALARGS() - - class Tuple(Evaluable): def __init__(self, items): self.items = items super().__init__(items) - @staticmethod - def evalf(*items): - return items - def _compile_expression(self, py_self, *items): return _pyast.Tuple(items) @@ -1029,9 +832,6 @@ def _simplified(self): def eval(self, /, **evalargs): return self.value - def evalf(self): - return self.value - def _compile(self, builder): # `self.value` is always a `numpy.ndarray`. If the array is 0d, we # convert the array to a `numpy.number` (`self.value[()]`), which @@ -1308,9 +1108,6 @@ def _invaxes(self): def _simplified(self): return self.func._transpose(self.axes) - def evalf(self, arr): - return arr.transpose(self.axes) - def _compile_expression(self, py_self, func): axes = _pyast.Tuple(tuple(map(_pyast.LiteralInt, self.axes))) return _pyast.Variable('numpy').get_attr('transpose').call(func, axes) @@ -1463,7 +1260,6 @@ class Product(Array): def __init__(self, func: Array): assert isinstance(func, Array), f'func={func!r}' self.func = func - self.evalf = functools.partial(numpy.all if func.dtype == bool else numpy.prod, axis=-1) super().__init__(args=(func,), shape=func.shape[:-1], dtype=func.dtype) def _compile_expression(self, py_self, func): @@ -1608,8 +1404,6 @@ def _optimized_for_numpy(self): r = tuple(range(self.ndim)) return Einsum(tuple(self.funcs), (r, r), r) - evalf = staticmethod(numpy.multiply) - def _compile_expression(self, py_self, func1, func2): return _pyast.BinOp(func1, '*', func2) @@ -1804,9 +1598,6 @@ def _simplified(self): # # We instead rely on Inflate._add to handle this situation. - - evalf = staticmethod(numpy.add) - def _compile(self, builder): if any(builder._ndependents[func] == 1 and func._compile_supports_iadd for func in self.funcs): out, alloc_block_id = builder.new_empty_array_for_evaluable(self) @@ -1903,11 +1694,6 @@ def __init__(self, args: typing.Tuple[Array, ...], args_idx: typing.Tuple[typing self._has_summed_axes = len(lengths) > len(out_idx) super().__init__(args=self.args, shape=shape, dtype=dtype) - def evalf(self, *args): - if self._has_summed_axes: - args = tuple(numpy.asarray(arg, order='F') for arg in args) - return numpy.core.multiarray.c_einsum(self._einsumfmt, *args) - def _compile_expression(self, py_self, *args): return _pyast.Variable('numpy').get_attr('core').get_attr('multiarray').get_attr('c_einsum').call(_pyast.LiteralStr(self._einsumfmt), *args) @@ -2019,10 +1805,6 @@ def _optimized_for_numpy(self): axes = [i-(i>rmaxis) for i in axes[:-1]] return transpose(Einsum(func.args, args_idx, func.out_idx[:rmaxis] + func.out_idx[rmaxis+1:]), axes) - @staticmethod - def evalf(arr): - return numpy.einsum('...kk->...k', arr, optimize=False) - def _compile_expression(self, py_self, arr): return _pyast.Variable('numpy').get_attr('core').get_attr('multiarray').get_attr('c_einsum').call(_pyast.LiteralStr('...kk->...k'), arr) @@ -2069,10 +1851,6 @@ def _simplified(self): if axis == self.func.ndim - 1: return util.sum(Inflate(func, dofmap, self.func.shape[-1])._take(self.indices, self.func.ndim - 1) for dofmap, func in parts.items()) - @staticmethod - def evalf(arr, indices): - return arr[..., indices] - def _compile_expression(self, py_self, arr, indices): return _pyast.Variable('numpy').get_attr('take').call(arr, indices, axis=_pyast.LiteralInt(-1)) @@ -2126,8 +1904,6 @@ def _optimized_for_numpy(self): elif p == -2: return Reciprocal(self.func * self.func) - evalf = staticmethod(numpy.power) - def _compile_expression(self, py_self, func, power): return _pyast.Variable('numpy').get_attr('power').call(func, power) @@ -2255,14 +2031,12 @@ def _derivative(self, var, seen): class Reciprocal(Holomorphic): - evalf = staticmethod(numpy.reciprocal) def _compile_expression(self, py_self, value): return _pyast.Variable('numpy').get_attr('reciprocal').call(value) class Negative(Pointwise): - evalf = staticmethod(numpy.negative) def _compile_expression(self, py_self, value): return _pyast.UnaryOp('-', value) @@ -2278,7 +2052,6 @@ def _intbounds_impl(self): class FloorDivide(Pointwise): - evalf = staticmethod(numpy.floor_divide) def _compile_expression(self, py_self, dividend, divisor): return _pyast.BinOp(dividend, '//', divisor) @@ -2311,7 +2084,6 @@ def _intbounds_impl(self): class Absolute(Pointwise): - evalf = staticmethod(numpy.absolute) def _compile_expression(self, py_self, value): return _pyast.Variable('numpy').get_attr('absolute').call(value) @@ -2418,7 +2190,6 @@ class Log(Holomorphic): class Mod(Pointwise): - evalf = staticmethod(numpy.mod) def _compile_expression(self, py_self, dividend, divisor): return _pyast.BinOp(dividend, '%', divisor) @@ -2613,9 +2384,6 @@ def _simplified(self): class Cast(Pointwise): - def evalf(self, arg): - return numpy.array(arg, dtype=self.dtype) - def _compile_expression(self, py_self, arg): return _pyast.Variable('numpy').get_attr('array').call(arg, dtype=_pyast.Variable(self.dtype.__name__)) @@ -2878,10 +2646,6 @@ def _simplified(self): # Tuple and its components be exposed to the function tree. return self.arrays[self.index] - def evalf(self, arrays): - assert isinstance(arrays, tuple) - return arrays[self.index] - def _compile_expression(self, py_self, arrays): return arrays.get_item(_pyast.LiteralInt(self.index)) @@ -2920,9 +2684,6 @@ def __init__(self, shape, dtype): def _unaligned(self): return Zeros((), self.dtype), () - def evalf(self, *shape): - return numpy.zeros(shape, dtype=self.dtype) - def _compile_expression(self, py_self, *shape): return _pyast.Variable('numpy').get_attr('zeros').call(_pyast.Tuple(shape), dtype=_pyast.Variable(self.dtype.__name__)) @@ -3296,10 +3057,6 @@ def __init__(self, fun: Array): def isconstant(self): return False # avoid simplifications based on fun being constant - @staticmethod - def evalf(dat): - return dat - def _compile(self, builder): return builder.compile(self.fun) @@ -3315,10 +3072,6 @@ def __init__(self, where: Array): self.where = where super().__init__(args=(where,), shape=(Sum(BoolToInt(where)),), dtype=int) - @staticmethod - def evalf(where): - return where.nonzero()[0] - def _compile_expression(self, py_self, where): return _pyast.Variable('numpy').get_attr('nonzero').call(where).get_item(_pyast.LiteralInt(0)) @@ -3366,10 +3119,6 @@ def __init__(self, func: Array, var: DerivativeTargetBase, derivative: Array) -> def arguments(self): return self._func.arguments | {self._var} - @staticmethod - def evalf(func: numpy.ndarray) -> numpy.ndarray: - return func - def _compile(self, builder): return builder.compile(self._func) @@ -3478,9 +3227,6 @@ def __init__(self, identifier, shape): self.identifier = identifier super().__init__(args=(), shape=shape, dtype=float) - def evalf(self): - raise Exception('{} cannot be evaluabled'.format(type(self).__name__)) - def _compile(self, builder): raise Exception(f'{type(self).__name__} cannot be evaluated') @@ -3823,9 +3569,6 @@ def __init__(self, ncoeffs: Array, nvars: int) -> None: self.nvars = nvars super().__init__(args=(ncoeffs,), shape=(), dtype=int) - def evalf(self, ncoeffs): - return numpy.array(poly.degree(self.nvars, ncoeffs.__index__())) - def _compile_expression(self, py_self, ncoeffs): ncoeffs = ncoeffs.get_attr('__index__').call() degree = _pyast.Variable('poly').get_attr('degree').call(_pyast.LiteralInt(self.nvars), ncoeffs) @@ -3867,9 +3610,6 @@ def __init__(self, nvars: int, degree: Array) -> None: self.degree = degree super().__init__(args=(degree,), shape=(), dtype=int) - def evalf(self, degree): - return numpy.array(poly.ncoeffs(self.nvars, degree.__index__())) - def _compile_expression(self, py_self, degree): degree = degree.get_attr('__index__').call() ncoeffs = _pyast.Variable('poly').get_attr('ncoeffs').call(_pyast.LiteralInt(self.nvars), degree) @@ -4082,10 +3822,6 @@ def __init__(self, index: Array, *choices: Array): self.choices = choices super().__init__(args=(index,)+choices, shape=shape, dtype=dtype) - @staticmethod - def evalf(index, *choices): - return numpy.choose(index, choices) - def _compile_expression(self, py_self, index, *choices): return _pyast.Variable('numpy').get_attr('choose').call(index, _pyast.Tuple(choices)) @@ -4418,8 +4154,7 @@ def __init__(self, loop_id: _LoopId, length: Array, init_arg: Evaluable, body_ar self.body_arg = body_arg if self.index in init_arg.arguments: raise ValueError('the loop initialization arguments must not depend on the index') - self._invariants, self._dependencies = _dependencies_sans_invariants(body_arg, self.index) - super().__init__(args=(length, init_arg, *self._invariants), *args, **kwargs) + super().__init__(args=(length, init_arg, body_arg), *args, **kwargs) @cached_property def _loop_block_id(self): @@ -4432,64 +4167,6 @@ def _compile_block_id(self): block_id = self._loop_block_id return *block_id[:-1], block_id[-1] + 1 - @cached_property - def _serialized_loop(self): - indices = {d: i for i, d in enumerate(itertools.chain([self.index], self._invariants, self._dependencies))} - return tuple((dep, tuple(map(indices.__getitem__, dep._Evaluable__args))) for dep in self._dependencies) - - @cached_property - def _serialized_loop_evalf(self): - return tuple((dep.evalf, indices) for dep, indices in self._serialized_loop) - - def evalf(self, length, init_arg, *invariants): - serialized_evalf = self._serialized_loop_evalf - output = self.evalf_loop_init(init_arg) - length = length.__index__() - values = [None] + list(invariants) + [None] * len(serialized_evalf) - with log.context(f'loop {self.index.loop_id}'.replace('{', '{{').replace('}', '}}') + ' {:3.0f}%', 0) as log_ctx: - fork = parallel.fork(length) - if fork: - raw_index = multiprocessing.RawValue('i', 0) - lock = multiprocessing.Lock() - with fork as pid: - with lock: - index = raw_index.value - raw_index.value = index + 1 - while index < length: - if not pid: - log_ctx(100*index/length) - values[0] = numpy.array(index) - for o, (op_evalf, indices) in enumerate(serialized_evalf, len(invariants) + 1): - values[o] = op_evalf(*[values[i] for i in indices]) - with lock: - self.evalf_loop_body(output, values[-1]) - index = raw_index.value - raw_index.value = index + 1 - else: - for index in range(length): - values[0] = numpy.array(index) - for o, (op_evalf, indices) in enumerate(serialized_evalf, len(invariants) + 1): - values[o] = op_evalf(*[values[i] for i in indices]) - self.evalf_loop_body(output, values[-1]) - log_ctx(100*(index+1)/length) - return output - - def evalf_withtimes(self, times, length, init_arg, *invariants): - serialized = self._serialized_loop - subtimes = times.setdefault(self, collections.defaultdict(_Stats)) - output = self.evalf_loop_init(init_arg) - values = [None] + list(invariants) + [None] * len(serialized) - for index in range(length): - values[0] = numpy.array(index) - for o, (op, indices) in enumerate(serialized, len(invariants) + 1): - values[o] = op.evalf_withtimes(subtimes, *[values[i] for i in indices]) - self.evalf_loop_body_withtimes(subtimes, output, values[-1]) - return output - - def evalf_loop_body_withtimes(self, times, output, body_arg): - with times[self]: - self.evalf_loop_body(output, body_arg) - def _node(self, cache, subgraph, times, unique_loop_ids): if (cached := cache.get(self)) is not None: return cached @@ -4526,48 +4203,6 @@ def _loops(self): deps |= self.body_arg._loops return deps.view() - @property - def _loop_deps(self): - deps = util.IDSet([self]) - deps |= self.init_arg._loop_deps - for arg in self._invariants: - deps |= arg._loop_deps - return deps.view() - - -class _LoopTuple(Loop): - - def __init__(self, loops: typing.Tuple[Loop], loop_id: _LoopId, length: Array): - assert isinstance(loops, tuple) and all(isinstance(loop, Loop) and loop.loop_id == loop_id and loop.length == length for loop in loops), f'loops={loops}' - self.loops = loops - super().__init__( - loop_id=loop_id, - length=length, - init_arg=Tuple(tuple(loop.init_arg for loop in loops)), - body_arg=Tuple(tuple(loop.body_arg for loop in loops)), - ) - - def evalf_loop_init(self, args): - return tuple(loop.evalf_loop_init(arg) for loop, arg in zip(self.loops, args)) - - def evalf_loop_body(self, outputs, args): - for loop, output, arg in zip(self.loops, outputs, args): - loop.evalf_loop_body(output, arg) - - def evalf_loop_body_withtimes(self, times, outputs, args): - for loop, output, arg in zip(self.loops, outputs, args): - loop.evalf_loop_body_withtimes(times, output, arg) - - def _node_loop_body(self, cache, subgraph, times): - if (cached := cache.get(self)) is not None: - return cached - cache[self] = node = TupleNode(tuple(item._node_loop_body(cache, subgraph, times) for item in self.loops), metadata=(type(self).__name__, times[self]), subgraph=subgraph) - return node - - @property - def _intbounds_tuple(self): - return tuple(loop._intbounds for loop in self.loops) - class LoopSum(Loop, Array): @@ -4599,13 +4234,6 @@ def _compile_iadd(self, builder, out, zeroed_block_id): def _compile_supports_iadd(self) -> bool: return True - def evalf_loop_init(self, shape): - return parallel.shzeros(tuple(n.__index__() for n in shape), dtype=self.dtype) - - @staticmethod - def evalf_loop_body(output, func): - output += func - def _derivative(self, var, seen): return loop_sum(derivative(self.func, var, seen), self.index) @@ -4732,14 +4360,6 @@ def _compile_iadd(self, builder, out, zeroed_block_id): out_slice = out.get_item(_pyast.Tuple((_pyast.Raw('...'), _pyast.Variable('slice').call(start, stop)))) builder.compile_iadd(self.func, out_slice, body_block_id) - def evalf_loop_init(self, shape): - return parallel.shempty(tuple(n.__index__() for n in shape), dtype=self.dtype) - - @staticmethod - def evalf_loop_body(output, arg): - func, start, stop = arg - output[..., start:stop] = func - def _derivative(self, var, seen): return Transpose.from_end(loop_concatenate(Transpose.to_end(derivative(self.func, var, seen), self.ndim-1), self.index), self.ndim-1) @@ -4880,28 +4500,6 @@ def _isunique(array): return numpy.unique(array).size == array.size -_AddDependency = collections.namedtuple('_AddDependency', ['dependency']) - -def _dependencies_sans_invariants(func, arg): - invariants = [] - dependencies = [] - cache = {arg} - stack = [func] - while stack: - func_ = stack.pop() - if isinstance(func_, _AddDependency): - dependencies.append(func_.dependency) - elif func_ not in cache: - cache.add(func_) - if arg in func_.arguments: - stack.append(_AddDependency(func_)) - stack.extend(func_._Evaluable__args) - else: - invariants.append(func_) - assert (dependencies or invariants or [arg])[-1] == func - return tuple(invariants), tuple(dependencies) - - def _make_loop_ids_unique(funcs: typing.Tuple[Evaluable, ...]) -> typing.Tuple[Evaluable, ...]: # Replaces all `_LoopId` instances such that every distinct `Loop` has its # own loop id. diff --git a/tests/test_evaluable.py b/tests/test_evaluable.py index e9a3c7d59..39b5e7b6b 100644 --- a/tests/test_evaluable.py +++ b/tests/test_evaluable.py @@ -102,15 +102,6 @@ def test_eval(self): actual=self.actual, desired=self.desired) - @unittest.skipIf(sys.version_info < (3, 7), 'time.perf_counter_ns is not available') - def test_eval_withtimes(self): - evalargs = dict(zip(self.arg_names, self.arg_values)) - without_times = self.actual.eval(**evalargs) - stats = collections.defaultdict(evaluable._Stats) - with_times = self.actual.eval_withtimes(stats, **evalargs) - self.assertArrayAlmostEqual(with_times, without_times, 15) - self.assertIn(self.actual, stats) - def test_getitem(self): for idim in range(self.actual.ndim): for item in range(self.desired.shape[idim]): @@ -407,10 +398,6 @@ def test_node(self): with self.subTest('from-cache'): if node: self.assertEqual(self.actual._node(cache, None, times, False), node) - with self.subTest('with-times'): - times = collections.defaultdict(evaluable._Stats) - self.actual.eval_withtimes(times, **dict(zip(self.arg_names, self.arg_values))) - self.actual._node(cache, None, times, False) def generate(*shape, real, imag, zero, negative): @@ -1078,79 +1065,6 @@ def _simplified(self): t.simplified -class combine_loops(TestCase): - - @staticmethod - def _combine(*loops): - assert loops - index = loops[0].index - assert all(loop.index == index for loop in loops[1:]) - combined = evaluable._LoopTuple(loops, index.loop_id, index.length) - return [evaluable.ArrayFromTuple(combined, i, loop.shape, loop.dtype) for i, loop in enumerate(loops)] - - def test_same_index(self): - i = evaluable.loop_index('i', 3) - A = evaluable.loop_concatenate(evaluable.InsertAxis(i, evaluable.constant(1)), i) - B = evaluable.loop_sum(i, i) - C = A + evaluable.appendaxes(B, A.shape) - - Ac, Bc = self._combine(A, B) - Cc = Ac + evaluable.appendaxes(Bc, Ac.shape) - - self.assertEqual(C._combine_loops(), Cc) - - def test_different_index(self): - i = evaluable.loop_index('i', 3) - j = evaluable.loop_index('j', 3) - A = evaluable.loop_concatenate(evaluable.InsertAxis(i, evaluable.constant(1)), i) - B = evaluable.loop_sum(j, j) - C = A + evaluable.appendaxes(B, A.shape) - - self.assertEqual(C._combine_loops(), C) - - def test_invariant(self): - i = evaluable.loop_index('i', 3) - A = evaluable.loop_sum(i, i) - B = evaluable.loop_sum(i + 1, i) - C = evaluable.loop_sum(A + B, i) - - Ac, Bc = self._combine(A, B) - Cc = evaluable.loop_sum(Ac + Bc, i) - - with self.subTest('default candidates'): - self.assertEqual(C._combine_loops(), Cc) - with self.subTest('custom candidates'): - self.assertEqual(C._combine_loops(util.IDSet([A, B, C])), Cc) - - def test_nested(self): - i = evaluable.loop_index('i', 3) - j = evaluable.loop_index('j', 3) - A = evaluable.loop_sum(i + j, i) - B = evaluable.loop_sum(i + j + 1, i) - C = evaluable.loop_sum(A + B, j) - - Ac, Bc = self._combine(A, B) - Cc = evaluable.loop_sum(Ac + Bc, index=j) - - self.assertEqual(C._combine_loops(), Cc) - - def test_invariant_and_nested(self): - i = evaluable.loop_index('i', 3) - j = evaluable.loop_index('j', 3) - A = evaluable.loop_sum(i + j, i) - B = evaluable.loop_sum(i + j + 1, i) - C = evaluable.loop_sum(i + j + 2, i) - D = evaluable.loop_sum(A + B, j) - E = evaluable.loop_sum(B + C, j) - F = D + E - - Ac, Bc, Cc = self._combine(A, B, C) - Dc, Ec = self._combine(evaluable.loop_sum(Ac + Bc, j), evaluable.loop_sum(Bc + Cc, j)) - Fc = Dc + Ec - - self.assertEqual(F._combine_loops(), Fc) - - class make_loop_ids_unique(TestCase): @staticmethod