def test_default_rules(self, order): """ Test that the default replacement rules return the same as standard FD. """ grid = Grid(shape=(20, 20)) u0 = TimeFunction(name='u', grid=grid, time_order=order, space_order=order) u1 = TimeFunction(name='u', grid=grid, time_order=order, space_order=order, coefficients='symbolic') eq0 = Eq(-u0.dx+u0.dt) eq1 = Eq(u1.dt-u1.dx) assert(eq0.evalf(_PRECISION).__repr__() == eq1.evalf(_PRECISION).__repr__())
def test_dynamic_nthreads(self): grid = Grid(shape=(16, 16, 16)) f = TimeFunction(name='f', grid=grid) op = Operator(Eq(f.forward, f + 1.), dle='openmp') # Check num_threads appears in the generated code # Not very elegant, but it does the trick assert 'num_threads(nthreads)' in str(op) # Check `op` accepts the `nthreads` kwarg op.apply(time=0) op.apply(time_m=1, time_M=1, nthreads=4) assert np.all(f.data[0] == 2.) # Check the actual value assumed by `nthreads` assert op.arguments(time=0)['nthreads'] == NThreads.default_value() assert op.arguments(time=0, nthreads=123)['nthreads'] == 123 # user supplied
def test_precomputed2(self): shape = (101, 101) grid = Grid(shape=shape) x, y = grid.dimensions r = 2 # Constant for linear interpolation # because we interpolate across 2 neighbouring points in each dimension nt = 10 m = TimeFunction(name="m", grid=grid, space_order=0, save=None, time_order=1) m.data[:] = 0.0 m.data[:, 40, 40] = 1.0 matrix = scipy.sparse.eye(1, dtype=np.float32) sf = MatrixSparseTimeFunction(name="s", grid=grid, r=r, matrix=matrix, nt=nt) # Lookup the exact point sf.gridpoints.data[0, 0] = 40 sf.gridpoints.data[0, 1] = 40 sf.interpolation_coefficients[x].data[0, 0] = 1.0 sf.interpolation_coefficients[x].data[0, 1] = 2.0 sf.interpolation_coefficients[y].data[0, 0] = 1.0 sf.interpolation_coefficients[y].data[0, 1] = 2.0 sf.data[:] = 0.0 step = [Eq(m.forward, m)] interp = sf.interpolate(m) op = Operator(step + interp) sf.manual_scatter() op(time_m=0, time_M=0) sf.manual_gather() assert sf.data[0, 0] == 1.0
def setup(self): grid = Grid(shape=(5, 5, 5)) funcs = [Function(name='f%d' % n, grid=grid) for n in range(30)] tfuncs = [TimeFunction(name='u%d' % n, grid=grid) for n in range(30)] stfuncs = [ SparseTimeFunction(name='su%d' % n, grid=grid, npoint=1, nt=100) for n in range(30) ] v = TimeFunction(name='v', grid=grid, space_order=2) eq = Eq(v.forward, v.laplace + sum(funcs) + sum(tfuncs) + sum(stfuncs), subdomain=grid.interior) self.op = Operator(eq, opt='noop') # Allocate data, populate cached properties, etc. self.op.arguments(time_M=98)
def test_discarding_runs(): grid = Grid(shape=(64, 64, 64)) f = TimeFunction(name='f', grid=grid) op = Operator(Eq(f.forward, f + 1.), dle=('advanced', {'openmp': True})) op.apply(time=100, nthreads=4, autotune='aggressive') assert op._state['autotuning'][0]['runs'] == 18 assert op._state['autotuning'][0]['tpr'] == options['squeezer'] + 1 assert len(op._state['autotuning'][0]['tuned']) == 3 assert op._state['autotuning'][0]['tuned']['nthreads'] == 4 # With 1 < 4 threads, the AT eventually tries many more combinations op.apply(time=100, nthreads=1, autotune='aggressive') assert op._state['autotuning'][1]['runs'] == 25 assert op._state['autotuning'][1]['tpr'] == options['squeezer'] + 1 assert len(op._state['autotuning'][1]['tuned']) == 3 assert op._state['autotuning'][1]['tuned']['nthreads'] == 1
def test_explicit_run(self): time_dim = 6 grid = Grid(shape=(11, 11)) a = TimeFunction(name='a', grid=grid, time_order=1, time_dim=time_dim, save=True) eqn = Eq(a.forward, a + 1.) op = Operator(eqn) assert isinstance(op, OperatorForeign) args = OrderedDict(op.arguments()) assert args['a'] is None # Emulate data feeding from outside array = np.ndarray(shape=a.shape, dtype=np.float32) array.fill(0.0) args['a'] = array op.cfunction(*list(args.values())) assert all(np.allclose(args['a'][i], i) for i in range(time_dim))
def td_born_forward_op(model, geometry, time_order, space_order): nt = geometry.nt # Define the wavefields with the size of the model and the time dimension u0 = TimeFunction( name='u0', grid=model.grid, time_order=time_order, space_order=space_order, save=nt ) u = TimeFunction( name='u', grid=model.grid, time_order=time_order, space_order=space_order, save=nt ) dm = TimeFunction( name='dm', grid=model.grid, time_order=time_order, space_order=space_order, save=nt ) # Define the wave equation pde = model.m * u.dt2 - u.laplace + model.damp * u.dt - dm * u0 # Use `solve` to rearrange the equation into a stencil expression stencil = Eq(u.forward, solve(pde, u.forward), subdomain=model.grid.subdomains['physdomain']) # Sample at receivers born_data_rec = PointSource( name='born_data_rec', grid=model.grid, time_range=geometry.time_axis, coordinates=geometry.rec_positions ) rec_term = born_data_rec.interpolate(expr=u) return Operator([stencil] + rec_term, subs=model.spacing_map)
def test_interior(self): """ Tests application of an Operator consisting of a single equation over the ``INTERIOR`` region. """ grid = Grid(shape=(4, 4, 4)) x, y, z = grid.dimensions u = TimeFunction(name='u', grid=grid) eqn = [Eq(u.forward, u + 2, region=INTERIOR)] op = Operator(eqn, dle='noop') op.apply(time_M=2) assert np.all(u.data[1, 1:-1, 1:-1, 1:-1] == 6.) assert np.all(u.data[1, :, 0] == 0.) assert np.all(u.data[1, :, -1] == 0.) assert np.all(u.data[1, :, :, 0] == 0.) assert np.all(u.data[1, :, :, -1] == 0.)
def test_mixed_space_order(self): """ Make sure that no matter whether data objects have different space order, as long as they have same domain, the Operator will be executed correctly. """ grid = Grid(shape=(8, 8, 8)) u = TimeFunction(name='yu4D', grid=grid, space_order=0) v = TimeFunction(name='yv4D', grid=grid, space_order=1) u.data_with_halo[:] = 1. v.data_with_halo[:] = 2. op = Operator(Eq(v.forward, u + v)) op(yu4D=u, yv4D=v, t=0) assert 'run_solution' in str(op) # Chech that the domain size has actually been written to assert np.all(v.data[1] == 3.) # Check that the halo planes are untouched assert np.all(v.data_with_halo[1, 0, :, :] == 2) assert np.all(v.data_with_halo[1, :, 0, :] == 2) assert np.all(v.data_with_halo[1, :, :, 0] == 2)
def test_increasing_halo_wo_ofs(self, space_order, nosimd): """ Apply the trivial equation ``u[t+1,x,y,z] = u[t,x,y,z] + 1`` and check that increasing space orders lead to proportionately larger halo regions, which are *not* written by the Operator. For example, with ``space_order = 0``, produce (in 2D view): 1 1 1 ... 1 1 1 1 1 ... 1 1 1 1 1 ... 1 1 1 1 1 ... 1 1 1 1 1 ... 1 1 With ``space_order = 1``, produce: 0 0 0 0 0 0 0 0 0 0 1 1 1 ... 1 1 0 0 1 1 1 ... 1 1 0 0 1 1 1 ... 1 1 0 0 1 1 1 ... 1 1 0 0 1 1 1 ... 1 1 0 0 0 0 0 0 0 0 0 0 And so on and so forth. """ # SIMD on/off configuration['develop-mode'] = nosimd grid = Grid(shape=(16, 16, 16)) u = TimeFunction(name='yu4D', grid=grid, space_order=space_order) u.data_with_halo[:] = 0. op = Operator(Eq(u.forward, u + 1.)) op(yu4D=u, time=0) assert 'run_solution' in str(op) # Chech that the domain size has actually been written to assert np.all(u.data[1] == 1.) # Check that the halo planes are still 0 assert all(np.all(u.data_with_halo[1, i, :, :] == 0) for i in range(u._size_halo.left[1])) assert all(np.all(u.data_with_halo[1, :, i, :] == 0) for i in range(u._size_halo.left[2])) assert all(np.all(u.data_with_halo[1, :, :, i] == 0) for i in range(u._size_halo.left[3]))
def test_default_rules_vs_string(self, so, expected): """ Test that default_rules generates correct symbolic expressions when used with staggered grids. """ grid = Grid(shape=(11, ), extent=(10., )) x = grid.dimensions[0] f = Function(name='f', grid=grid, space_order=so, staggered=NODE, coefficients='symbolic') g = Function(name='g', grid=grid, space_order=so, staggered=x, coefficients='symbolic') eq = Eq(g, f.dx) assert str(eq.evaluate.rhs) == expected
def test_conddim_w_shifting(): nt = 50 grid = Grid(shape=(5, 5)) time = grid.time_dim factor = Constant(name='factor', value=5, dtype=np.int32) t_sub = ConditionalDimension('t_sub', parent=time, factor=factor) save_shift = Constant(name='save_shift', dtype=np.int32) u = TimeFunction(name='u', grid=grid, time_order=0) u1 = TimeFunction(name='u', grid=grid, time_order=0) usave = TimeFunction(name='usave', grid=grid, time_order=0, save=(int(nt // factor.data)), time_dim=t_sub) for i in range(usave.save): usave.data[i, :] = i eqns = Eq(u.forward, u + usave.subs(t_sub, t_sub - save_shift)) op0 = Operator(eqns, opt='noop') op1 = Operator(eqns, opt='buffering') # Check generated code assert len(retrieve_iteration_tree(op1)) == 3 buffers = [i for i in FindSymbols().visit(op1) if i.is_Array] assert len(buffers) == 1 # From time_m=15 to time_M=35 with a factor=5 -- it means that, thanks # to t_sub, we enter the Eq exactly (35-15)/5 + 1 = 5 times. We set # save_shift=1 so instead of accessing the range usave[15/5:35/5+1], # we rather access the range usave[15/5-1:35:5], which means accessing # the usave values 2, 3, 4, 5, 6. op0.apply(time_m=15, time_M=35, save_shift=1) op1.apply(time_m=15, time_M=35, save_shift=1, u=u1) assert np.allclose(u.data, 20) assert np.all(u.data == u1.data) # Again, but with a different shift op1.apply(time_m=15, time_M=35, save_shift=-2, u=u1) assert np.allclose(u1.data, 20 + 35)
def test_misc_dims(self): """ Test MPI in presence of Functions with mixed distributed/replicated Dimensions, with only a strict subset of the Grid dimensions used. """ dx = Dimension(name='dx') grid = Grid(shape=(4, 4)) x, y = grid.dimensions glb_pos_map = grid.distributor.glb_pos_map time = grid.time_dim u = TimeFunction(name='u', grid=grid, time_order=1, space_order=2, save=4) c = Function(name='c', grid=grid, dimensions=(x, dx), shape=(4, 5)) step = Eq(u.forward, (u[time, x - 2, y] * c[x, 0] + u[time, x - 1, y] * c[x, 1] + u[time, x, y] * c[x, 2] + u[time, x + 1, y] * c[x, 3] + u[time, x + 2, y] * c[x, 4])) for i in range(4): c.data[i, 0] = 1.0 + i c.data[i, 1] = 1.0 + i c.data[i, 2] = 3.0 + i c.data[i, 3] = 6.0 + i c.data[i, 4] = 5.0 + i u.data[:] = 0.0 u.data[0, 2, :] = 2.0 op = Operator(step) op(time_m=0, time_M=0) if LEFT in glb_pos_map[x]: assert (np.all(u.data[1, 0, :] == 10.0)) assert (np.all(u.data[1, 1, :] == 14.0)) else: assert (np.all(u.data[1, 2, :] == 10.0)) assert (np.all(u.data[1, 3, :] == 8.0))
def test_hierarchical_blocking(): grid = Grid(shape=(64, 64, 64)) u = TimeFunction(name='u', grid=grid, space_order=2) op = Operator(Eq(u.forward, u + 1), dle=('blocking', {'openmp': False, 'blocklevels': 2})) # 'basic' mode op.apply(time_M=0, autotune='basic') assert op._state['autotuning'][0]['runs'] == 10 assert op._state['autotuning'][0]['tpr'] == options['squeezer'] + 1 assert len(op._state['autotuning'][0]['tuned']) == 4 # 'aggressive' mode op.apply(time_M=0, autotune='aggressive') assert op._state['autotuning'][1]['runs'] == 38 assert op._state['autotuning'][1]['tpr'] == options['squeezer'] + 1 assert len(op._state['autotuning'][1]['tuned']) == 4
def test_at_is_actually_working(shape, expected): """ Check that autotuning is actually running when switched on, in both 2D and 3D operators. """ grid = Grid(shape=shape) infield = Function(name='infield', grid=grid) infield.data[:] = np.arange(reduce(mul, shape), dtype=np.int32).reshape(shape) outfield = Function(name='outfield', grid=grid) stencil = Eq(outfield.indexify(), outfield.indexify() + infield.indexify() * 3.0) op = Operator(stencil, dle=('blocking', { 'openmp': False, 'blockinner': True, 'blockalways': True })) # Run with whatever `configuration` says (by default, basic+preemptive) op(infield=infield, outfield=outfield, autotune=True) assert op._state['autotuning'][-1]['runs'] == 4 assert op._state['autotuning'][-1]['tpr'] == 1 # Now try `aggressive` autotuning configuration['autotuning'] = 'aggressive' op(infield=infield, outfield=outfield, autotune=True) assert op._state['autotuning'][-1]['runs'] == expected assert op._state['autotuning'][-1]['tpr'] == 1 configuration['autotuning'] = configuration._defaults['autotuning'] # Try again, but using the Operator API directly op(infield=infield, outfield=outfield, autotune='aggressive') assert op._state['autotuning'][-1]['runs'] == expected assert op._state['autotuning'][-1]['tpr'] == 1 # Similar to above op(infield=infield, outfield=outfield, autotune=('aggressive', 'preemptive')) assert op._state['autotuning'][-1]['runs'] == expected assert op._state['autotuning'][-1]['tpr'] == 1
def test_collapsing(self): grid = Grid(shape=(3, 3, 3)) u = TimeFunction(name='u', grid=grid) op = Operator(Eq(u.forward, u + 1), opt=('blocking', 'openmp')) # Does it compile? Honoring the OpenMP specification isn't trivial assert op.cfunction # Does it produce the right result op.apply(t_M=9) assert np.all(u.data[0] == 10) iterations = FindNodes(Iteration).visit(op._func_table['bf0']) assert iterations[0].pragmas[0].value == 'omp for collapse(2) schedule(dynamic,1)' assert iterations[2].pragmas[0].value == ('omp parallel for collapse(2) ' 'schedule(dynamic,1) ' 'num_threads(nthreads_nested)')
def _new_operator3(shape, blockshape0=None, blockshape1=None, opt=None): blockshape0 = as_tuple(blockshape0) blockshape1 = as_tuple(blockshape1) grid = Grid(shape=shape, extent=shape, dtype=np.float64) # Allocate the grid and set initial condition # Note: This should be made simpler through the use of defaults u = TimeFunction(name='u', grid=grid, time_order=1, space_order=(2, 2, 2)) u.data[0, :] = np.linspace(-1, 1, reduce(mul, shape)).reshape(shape) # Derive the stencil according to devito conventions op = Operator(Eq(u.forward, 0.5 * u.laplace + u), opt=opt) blocksizes0 = get_blocksizes(op, opt, grid, blockshape0, 0) blocksizes1 = get_blocksizes(op, opt, grid, blockshape1, 1) op.apply(u=u, t=10, **blocksizes0, **blocksizes1) return u.data[1, :], op
def test_arguments_subrange(self): """ Test op.apply when a subrange is specified for a distributed dimension. """ grid = Grid(shape=(16, )) x = grid.dimensions[0] f = TimeFunction(name='f', grid=grid) op = Operator(Eq(f.forward, f + 1.)) op.apply(time=0, x_m=4, x_M=11) glb_pos_map = f.grid.distributor.glb_pos_map if LEFT in glb_pos_map[x]: assert np.all(f.data_ro_domain[1, :4] == 0.) assert np.all(f.data_ro_domain[1, 4:] == 1.) else: assert np.all(f.data_ro_domain[1, :-4] == 1.) assert np.all(f.data_ro_domain[1, -4:] == 0.)
def test_trivial_eq_1d_save(self): grid = Grid(shape=(32, )) x = grid.dimensions[0] time = grid.time_dim f = TimeFunction(name='f', grid=grid, save=5) f.data_with_halo[:] = 1. op = Operator(Eq(f.forward, f[time, x - 1] + f[time, x + 1] + 1)) op.apply() time_M = op.prepare_arguments()['time_M'] assert np.all(f.data_ro_domain[1] == 3.) glb_pos_map = f.grid.distributor.glb_pos_map if LEFT in glb_pos_map[x]: assert np.all(f.data_ro_domain[-1, time_M:] == 31.) else: assert np.all(f.data_ro_domain[-1, :-time_M] == 31.)
def test_mpi(): grid = Grid(shape=(4, 4)) u = TimeFunction(name='u', grid=grid, space_order=2) u1 = TimeFunction(name='u', grid=grid, space_order=2) eqn = Eq(u.forward, u.dx2 + 1.) op0 = Operator(eqn) op1 = Operator(eqn, opt=('advanced', {'linearize': True})) # Check generated code assert 'uL0' not in str(op0) assert 'uL0' in str(op1) op0.apply(time_M=10) op1.apply(time_M=10, u=u1) assert np.all(u.data == u1.data)
def test_at_is_actually_working(shape, expected): """ Check that autotuning is actually running when switched on, in both 2D and 3D operators. """ grid = Grid(shape=shape) buffer = StringIO() temporary_handler = logging.StreamHandler(buffer) logger.addHandler(temporary_handler) infield = Function(name='infield', grid=grid) infield.data[:] = np.arange(reduce(mul, shape), dtype=np.int32).reshape(shape) outfield = Function(name='outfield', grid=grid) stencil = Eq(outfield.indexify(), outfield.indexify() + infield.indexify() * 3.0) op = Operator(stencil, dle=('blocking', { 'blockinner': True, 'blockalways': True })) # Expected 3 AT attempts for the given shape op(infield=infield, outfield=outfield, autotune=True) out = [i for i in buffer.getvalue().split('\n') if 'AutoTuner:' in i] assert len(out) == 4 # Now try the same with aggressive autotuning, which tries 9 more cases configuration.core['autotuning'] = 'aggressive' op(infield=infield, outfield=outfield, autotune=True) out = [i for i in buffer.getvalue().split('\n') if 'AutoTuner:' in i] assert len(out) == expected configuration.core['autotuning'] = configuration.core._defaults[ 'autotuning'] logger.removeHandler(temporary_handler) temporary_handler.flush() temporary_handler.close() buffer.flush() buffer.close()
def test_multiple_subnests_v1(self): """ Unlike ``test_multiple_subnestes_v0``, now we use the ``cire-rotate=True`` option, which trades some of the inner parallelism for a smaller working set. """ grid = Grid(shape=(3, 3, 3)) x, y, z = grid.dimensions t = grid.stepping_dim f = Function(name='f', grid=grid) u = TimeFunction(name='u', grid=grid, space_order=3) eqn = Eq( u.forward, ((u[t, x, y, z] + u[t, x + 1, y + 1, z + 1]) * 3 * f + (u[t, x + 2, y + 2, z + 2] + u[t, x + 3, y + 3, z + 3]) * 3 * f + 1)) op = Operator(eqn, opt=('advanced', { 'openmp': True, 'cire-mincost-sops': 1, 'cire-rotate': True, 'par-nested': 0, 'par-collapse-ncores': 1, 'par-dynamic-work': 0 })) trees = retrieve_iteration_tree(op._func_table['bf0'].root) assert len(trees) == 2 assert trees[0][0] is trees[1][0] assert trees[0][0].pragmas[0].value ==\ 'omp for collapse(2) schedule(dynamic,1)' assert not trees[0][2].pragmas assert not trees[0][3].pragmas assert trees[0][4].pragmas[0].value == ('omp parallel for collapse(1) ' 'schedule(dynamic,1) ' 'num_threads(nthreads_nested)') assert not trees[1][2].pragmas assert trees[1][3].pragmas[0].value == ('omp parallel for collapse(1) ' 'schedule(dynamic,1) ' 'num_threads(nthreads_nested)')
def test_collapsing_v2(self): """ MFE from issue #1478. """ n = 8 m = 8 nx, ny, nchi, ncho = 12, 12, 1, 1 x, y = SpaceDimension("x"), SpaceDimension("y") ci, co = Dimension("ci"), Dimension("co") i, j = Dimension("i"), Dimension("j") grid = Grid((nx, ny), dtype=np.float32, dimensions=(x, y)) X = Function(name="xin", dimensions=(ci, x, y), shape=(nchi, nx, ny), grid=grid, space_order=n // 2) dy = Function(name="dy", dimensions=(co, x, y), shape=(ncho, nx, ny), grid=grid, space_order=n // 2) dW = Function(name="dW", dimensions=(co, ci, i, j), shape=(ncho, nchi, n, m), grid=grid) eq = [ Eq( dW[co, ci, i, j], dW[co, ci, i, j] + dy[co, x, y] * X[ci, x + i - n // 2, y + j - m // 2]) for i in range(n) for j in range(m) ] op = Operator(eq, opt=('advanced', {'openmp': True})) iterations = FindNodes(Iteration).visit(op) assert len(iterations) == 4 assert iterations[0].ncollapse == 1 assert iterations[1].is_Vectorized assert iterations[2].is_Sequential assert iterations[3].is_Sequential
def test_at_w_mpi(): """Make sure autotuning works in presence of MPI. MPI ranks work in isolation to determine the best block size, locally.""" grid = Grid(shape=(8, 8)) t = grid.stepping_dim x, y = grid.dimensions f = TimeFunction(name='f', grid=grid, time_order=1) f.data_with_halo[:] = 1. eq = Eq(f.forward, f[t, x-1, y] + f[t, x+1, y]) op = Operator(eq, dle=('advanced', {'openmp': False, 'blockinner': True})) op.apply(time=-1, autotune=('basic', 'runtime')) # Nothing really happened, as not enough timesteps assert np.all(f.data_ro_domain[0] == 1.) assert np.all(f.data_ro_domain[1] == 1.) # The 'destructive' mode writes directly to `f` for whatever timesteps required # to perform the autotuning. Eventually, the result is complete garbage; note # also that this autotuning mode disables the halo exchanges op.apply(time=-1, autotune=('basic', 'destructive')) assert np.all(f._data_ro_with_inhalo.sum() == 904) # Check the halo hasn't been touched during AT glb_pos_map = grid.distributor.glb_pos_map if LEFT in glb_pos_map[x]: assert np.all(f._data_ro_with_inhalo[:, -1] == 1) else: assert np.all(f._data_ro_with_inhalo[:, 0] == 1) # Finally, try running w/o AT, just to be sure nothing was broken f.data_with_halo[:] = 1. op.apply(time=2) if LEFT in glb_pos_map[x]: assert np.all(f.data_ro_domain[1, 0] == 5.) assert np.all(f.data_ro_domain[1, 1] == 7.) assert np.all(f.data_ro_domain[1, 2:4] == 8.) else: assert np.all(f.data_ro_domain[1, 4:6] == 8) assert np.all(f.data_ro_domain[1, 6] == 7) assert np.all(f.data_ro_domain[1, 7] == 5)
def test_misc_dims(self): """ Tests grid-independent :class:`Function`s, which require YASK's "misc" dimensions. """ dx = Dimension(name='dx') grid = Grid(shape=(10, 10)) x, y = grid.dimensions time = grid.time_dim u = TimeFunction(name='u', grid=grid, time_order=1, space_order=4, save=4) c = Function(name='c', dimensions=(x, dx), shape=(10, 5)) step = Eq(u.forward, (u[time, x - 2, y] * c[x, 0] + u[time, x - 1, y] * c[x, 1] + u[time, x, y] * c[x, 2] + u[time, x + 1, y] * c[x, 3] + u[time, x + 2, y] * c[x, 4])) for i in range(10): c.data[i, 0] = 1.0 + i c.data[i, 1] = 1.0 + i c.data[i, 2] = 3.0 + i c.data[i, 3] = 6.0 + i c.data[i, 4] = 5.0 + i u.data[:] = 0.0 u.data[0, 2, :] = 2.0 op = Operator(step) assert 'run_solution' in str(op) op(time_m=0, time_M=0) assert (np.all(u.data[1, 0, :] == 10.0)) assert (np.all(u.data[1, 1, :] == 14.0)) assert (np.all(u.data[1, 2, :] == 10.0)) assert (np.all(u.data[1, 3, :] == 8.0)) assert (np.all(u.data[1, 4, :] == 10.0)) assert (np.all(u.data[1, 5:10, :] == 0.0))
def test_full_shape_with_subdims(self): """ Like `test_full_alias_shape_after_blocking`, but SubDomains (and therefore SubDimensions) are used. Nevertheless, the temporary shape should still be dictated by the root Dimensions. """ grid = Grid(shape=(3, 3, 3)) x, y, z = grid.dimensions # noqa t = grid.stepping_dim f = Function(name='f', grid=grid) f.data_with_halo[:] = 1. u = TimeFunction(name='u', grid=grid, space_order=3) u.data_with_halo[:] = 0. # Leads to 3D aliases eqn = Eq(u.forward, ((u[t, x, y, z] + u[t, x+1, y+1, z+1])*3*f + (u[t, x+2, y+2, z+2] + u[t, x+3, y+3, z+3])*3*f + 1), subdomain=grid.interior) op0 = Operator(eqn, dse='noop', dle=('advanced', {'openmp': True})) op1 = Operator(eqn, dse='aggressive', dle=('advanced', {'openmp': True})) xi0_blk_size = op1.parameters[-3] yi0_blk_size = op1.parameters[-2] z_size = op1.parameters[4] # Check Array shape arrays = [i for i in FindSymbols().visit(op1._func_table['bf0'].root) if i.is_Array] assert len(arrays) == 1 a = arrays[0] assert len(a.dimensions) == 3 assert a.halo == ((1, 1), (1, 1), (1, 1)) assert Add(*a.symbolic_shape[0].args) == xi0_blk_size + 2 assert Add(*a.symbolic_shape[1].args) == yi0_blk_size + 2 assert Add(*a.symbolic_shape[2].args) == z_size + 2 # Check numerical output op0(time_M=1) exp = np.copy(u.data[:]) u.data_with_halo[:] = 0. op1(time_M=1) assert np.all(u.data == exp)
def conv(nx, ny, nchi, ncho, n, m): # Image size dt = np.float32 x, y, ci, co = (SpaceDimension("x"), SpaceDimension("y"), Dimension("ci"), Dimension("co")) grid = Grid((nchi, ncho, nx, ny), dtype=dt, dimensions=(ci, co, x, y)) # Image im_in = Function(name="imi", dimensions=(ci, x, y), shape=(nchi, nx, ny), grid=grid, space_order=n//2) # Output im_out = Function(name="imo", dimensions=(co, x, y), shape=(ncho, nx, ny), grid=grid, space_order=n//2) # Weights i, j = Dimension("i"), Dimension("j") W = Function(name="W", dimensions=(co, ci, i, j), shape=(ncho, nchi, n, m), grid=grid) # Popuate weights with deterministic values for i in range(ncho): for j in range(nchi): W.data[i, j, :, :] = np.linspace(i+j, i+j+(n*m), n*m).reshape(n, m) # Convlution conv = [Eq(im_out, im_out + sum([W[co, ci, i2, i1] * im_in[ci, x+i1-n//2, y+i2-m//2] for i1 in range(n) for i2 in range(m)]))] op = Operator(conv) op.cfunction # Initialize the input/output input_data = np.linspace(-1, 1, nx*ny*nchi).reshape(nchi, nx, ny) im_in.data[:] = input_data.astype(np.float32) im_out.data op() return im_out.data, im_in.data
def test_subdimleft_parallel(self): """ Tests application of an Operator consisting of a subdimension defined over different sub-regions, explicitly created through the use of :class:`SubDimension`s. This tests that flow direction is not being automatically inferred from whether the subdimension is on the left or right boundary. """ grid = Grid(shape=(20, 20)) x, y = grid.dimensions t = grid.stepping_dim thickness = 4 u = TimeFunction(name='u', save=None, grid=grid, space_order=0, time_order=1) xl = SubDimension.left(name='xl', parent=x, thickness=thickness) yi = SubDimension.middle(name='yi', parent=y, thickness_left=thickness, thickness_right=thickness) # Can be done in parallel eq = Eq(u[t + 1, xl, yi], u[t, xl, yi] + 1) op = Operator([eq]) iterations = FindNodes(Iteration).visit(op) assert all(i.is_Affine and i.is_Parallel for i in iterations if i.dim in [xl, yi]) op.apply(time_m=0, time_M=0) assert np.all(u.data[1, 0:thickness, 0:thickness] == 0) assert np.all(u.data[1, 0:thickness, -thickness:] == 0) assert np.all(u.data[1, 0:thickness, thickness:-thickness] == 1) assert np.all(u.data[1, thickness + 1:, :] == 0)
def test_cache_blocking_imperfect_nest_v2(blockinner): """ Test that a non-perfect Iteration nest is blocked correctly. This is slightly different than ``test_cache_blocking_imperfect_nest`` as here only one Iteration gets blocked. """ shape = (16, 16, 16) grid = Grid(shape=shape, dtype=np.float64) u = TimeFunction(name='u', grid=grid, space_order=2) u.data[:] = np.linspace(0, 1, reduce(mul, shape), dtype=np.float64).reshape(shape) eq = Eq(u.forward, 0.01*u.dy.dy) op0 = Operator(eq, opt='noop') op1 = Operator(eq, opt=('cire-sops', {'blockinner': blockinner})) op2 = Operator(eq, opt=('advanced-fsg', {'blockinner': blockinner})) # First, check the generated code trees = retrieve_iteration_tree(op2._func_table['bf0'].root) assert len(trees) == 2 assert len(trees[0]) == len(trees[1]) assert all(i is j for i, j in zip(trees[0][:2], trees[1][:2])) assert trees[0][2] is not trees[1][2] assert trees[0].root.dim.is_Incr assert trees[1].root.dim.is_Incr assert op2.parameters[6] is trees[0].root.step op0(time_M=0) u1 = TimeFunction(name='u1', grid=grid, space_order=2) u1.data[:] = np.linspace(0, 1, reduce(mul, shape), dtype=np.float64).reshape(shape) op1(time_M=0, u=u1) u2 = TimeFunction(name='u2', grid=grid, space_order=2) u2.data[:] = np.linspace(0, 1, reduce(mul, shape), dtype=np.float64).reshape(shape) op2(time_M=0, u=u2) assert np.allclose(u.data, u1.data, rtol=1e-07) assert np.allclose(u.data, u2.data, rtol=1e-07)
def test_argument_from_index_constant(self): nx, ny = 30, 30 grid = Grid(shape=(nx, ny)) x, y = grid.dimensions arbdim = Dimension('arb') u = TimeFunction(name='u', grid=grid, save=None, time_order=2, space_order=0) snap = Function(name='snap', dimensions=(arbdim, x, y), shape=(5, nx, ny), space_order=0) save_t = Constant(name='save_t', dtype=np.int32) save_slot = Constant(name='save_slot', dtype=np.int32) expr = Eq(snap.subs(arbdim, save_slot), u.subs(grid.stepping_dim, save_t)) op = Operator(expr) u.data[:] = 0.0 snap.data[:] = 0.0 u.data[0, 10, 10] = 1.0 op.apply(save_t=0, save_slot=1) assert snap.data[1, 10, 10] == 1.0
def test_default_functions(self): """ Test the default argument derivation for functions. """ grid = Grid(shape=(5, 6, 7)) f = TimeFunction(name='f', grid=grid) g = Function(name='g', grid=grid) op = Operator(Eq(g, g + f)) expected = { 'x_size': 5, 'x_m': 0, 'x_M': 4, 'y_size': 6, 'y_m': 0, 'y_M': 5, 'z_size': 7, 'z_m': 0, 'z_M': 6, 'f': f.data_allocated, 'g': g.data_allocated, } self.verify_arguments(op.arguments(time=4), expected) exp_parameters = ['f', 'g', 'x_m', 'x_M', 'x_size', 'y_m', 'y_M', 'y_size', 'z_m', 'z_M', 'z_size', 'time_m', 'time_M'] self.verify_parameters(op.parameters, exp_parameters)