def test_get_extrapolation_points(self, setup): """ Check that the points to be used for extrapolation are identified correctly. """ key = setup['key'] order = 4 cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) stencils = StencilSet(2, 0, bcs, cache=cache) out_l, out_r = stencils._get_outside(key) ext_l, ext_r, l_floor, r_floor = stencils._get_extrapolation_points( key, out_l, out_r) # Should be false in this case if setup['f']: assert l_floor or r_floor else: assert not l_floor or r_floor assert np.all(ext_l == setup['el']) assert np.all(ext_r == setup['er'])
def test_get_keys(self, order, setup): """ Test that the keys returned are correct """ cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' offset = setup['offset'] deriv = setup['deriv'] if setup['bcs'] == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) key_len = order + 1 min_left = 0.5 - order // 2 max_right = order // 2 - 0.5 else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) key_len = order + 3 min_left = -0.5 - order // 2 max_right = order // 2 + 0.5 stencils = StencilSet(deriv, offset, bcs, cache=cache) keys = stencils._get_keys() if offset == 0: assert keys.shape[0] == key_len**2 else: assert keys.shape[0] == key_len * (key_len + 1) left = keys[:, 0] right = keys[:, 1] max_left = 0 min_right = 0 if offset == -0.5: min_right = -0.5 elif offset == 0.5: max_left = 0.5 assert np.amax(left[~np.isnan(left)]) == max_left assert np.amin(left[~np.isnan(left)]) == min_left assert np.amax(right[~np.isnan(right)]) == max_right assert np.amin(right[~np.isnan(right)]) == min_right
def test_get_outside(self, setup): """Check that oints outside boundary are identified correctly""" cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' order = setup['ord'] key = (setup['l'], setup['r']) bcs = BoundaryConditions({2 * i + 1: 0 for i in range(1 + order // 2)}, order) # Quick test so only on one stencil type stencils = StencilSet(1, -0.5, bcs, cache=cache) points_l, points_r = stencils._get_outside(key) assert np.all(points_l == setup['el']) assert np.all(points_r == setup['er'])
def test_zero_handling(self, order, spec): """ Check that stencils with distances of zero evaluate correctly. """ # Unpack the spec bc_type = spec['bcs'] deriv = spec['deriv'] goffset = spec['goffset'] eoffset = spec['eoffset'] if bc_type == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' stencils = StencilSet(deriv, eoffset, bcs, cache=cache) lambdas = stencils.lambdaify max_ext_points = stencils.max_ext_points distances = np.full((10, 1, 1), -2 * order, dtype=float) if goffset == 0.5: distances[4, :, :] = 0.5 else: distances[4, :, :] = 0 data = get_data_inc_reciprocals(distances, 1, 'x', goffset, eoffset) add_distance_column(data) data = data.iloc[1:-1] data = get_n_pts(data, 'double', order, eoffset) grid = Grid(shape=(10, 1, 11), extent=(9, 0, 0)) s_dim = Dimension(name='s') ncoeffs = 2 * max_ext_points + 1 w_shape = grid.shape + (ncoeffs, ) w_dims = grid.dimensions + (s_dim, ) w = Function(name='w', dimensions=w_dims, shape=w_shape) fill_stencils(data, 'double', max_ext_points, lambdas, w, 10, 'x') # Derivative stencils should be zero if evaluation offset is zero assert (np.all(np.abs(w.data) < np.finfo(np.float).eps))
def test_stencil_evaluation(self, order, spec, inside, dist_val): """Check that stencil coefficients are selected and evaluated to expected values""" # Unpack the spec bc_type = spec['bcs'] deriv = spec['deriv'] goffset = spec['goffset'] eoffset = spec['eoffset'] if bc_type == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' # stencils_lambda = get_stencils_lambda(deriv, eoffset, bcs, cache=cache) stencils = StencilSet(deriv, eoffset, bcs, cache=cache) lambdas = stencils.lambdaify max_ext_points = stencils.max_ext_points # Generate a suitable 1D distance field, hitting several stencil types distances = np.full((10, 1, 1), -order // 2, dtype=float) ind = np.array([1, 2, 5]) distances[ind] = dist_val # Create a function to generate weights for grid = Grid(shape=(10, 1, 1), extent=(9, 0, 0)) x, y, z = grid.dimensions if goffset == 0.: f = Function(name='f', grid=grid, space_order=order) elif goffset == 0.5: f = Function(name='f', grid=grid, space_order=order, staggered=x) # Generate an accompanying section interior = np.full((10, 1, 1), False) if inside: # Generate one segmentation inner_points = np.array([0, 1, 3, 4, 5]) else: # Generate the other segmentation inner_points = np.array([2, 6, 7, 8, 9]) interior[inner_points] = True # Evaluate stencils w = get_component_weights(distances, 0, f, deriv, lambdas, interior, max_ext_points, eoffset) # Check against precalculated stencils # Generate filename filename = str(dist_val) + '_' + str(inside) + '_' + str( order) + '_' + bc_type + '_' + str(deriv) + '_' + str( goffset) + '_' + str(eoffset) # Load reference data reference = np.load( os.path.dirname(__file__) + '/evaluated_stencils/' + filename + '.npy') assert np.all(np.absolute(w.data - reference) < np.finfo(float).eps)
def test_zero_handling_staggered(self, side, order, spec): """ Check that stencils with distances of zero evaluate correctly for staggered systems. """ # Unpack the spec bc_type = spec['bcs'] deriv = spec['deriv'] goffset = spec['goffset'] eoffset = spec['eoffset'] if bc_type == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' # stencils_lambda = get_stencils_lambda(deriv, eoffset, bcs, cache=cache) stencils = StencilSet(deriv, eoffset, bcs, cache=cache) lambdas = stencils.lambdaify max_ext_points = stencils.max_ext_points distances = np.full((10, 1, 1), -2 * order, dtype=float) if goffset == 0.5: distances[4, :, :] = 0.5 else: distances[4, :, :] = 0 data = get_data_inc_reciprocals(distances, 1, 'x', goffset, eoffset) add_distance_column(data) right_dist = pd.notna(data.eta_l) left_dist = pd.notna(data.eta_r) data.loc[right_dist, 'dist'] = order data.loc[left_dist, 'dist'] = -order first = data.loc[left_dist] last = data.loc[right_dist] first = shift_grid_endpoint(first, 'x', goffset, eoffset) last = shift_grid_endpoint(last, 'x', goffset, eoffset) first = get_n_pts(first, 'first', order, eoffset) last = get_n_pts(last, 'last', order, eoffset) grid = Grid(shape=(10, 1, 1), extent=(9, 0, 0)) s_dim = Dimension(name='s') ncoeffs = 2 * max_ext_points + 1 w_shape = grid.shape + (ncoeffs, ) w_dims = grid.dimensions + (s_dim, ) w = Function(name='w', dimensions=w_dims, shape=w_shape) # Fill the weights using the standard stencil (for interior) if max_ext_points > order // 2: # Need to zero pad the standard stencil zero_pad = max_ext_points - order // 2 w.data[:, :, :, zero_pad:-zero_pad] = standard_stencil(deriv, order, offset=eoffset) else: w.data[:] = standard_stencil(deriv, order, offset=eoffset) if side == 'first': w.data[4:] = 0 fill_stencils(first, 'first', max_ext_points, lambdas, w, 10, 'x') if side == 'last': w.data[:5] = 0 fill_stencils(last, 'last', max_ext_points, lambdas, w, 10, 'x') # Check against a saved correct version # Generate filename filename = side + '_' + str(order) + '_' + bc_type + '_' + str( deriv) + '_' + str(goffset) + '_' + str(eoffset) # Load reference data reference = np.load( os.path.dirname(__file__) + '/zero_handling_stencils/' + filename + '.npy') assert np.all(np.absolute(w.data - reference) < np.finfo(float).eps)
def test_fill_stencils_offset(self, offset, point_type, order, spacing): """ Check that offsetting the grid and boundary by the same amount results in identical stencils for both cases. This is checked on both sides of the boundary. """ spec = {2 * i: 0 for i in range(1 + order // 2)} bcs = BoundaryConditions(spec, order) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' stencils = StencilSet(2, 0, bcs, cache=cache) lambdas = stencils.lambdaify max_ext_points = stencils.max_ext_points distances = np.full((10, 1, 10), -2 * order * spacing, dtype=float) distances[4, :, :] = np.linspace(0, 0.9 * spacing, 10) offset_distances = np.full((10, 1, 10), -2 * order * spacing, dtype=float) if offset == 0.5: # +ve stagger offset_distances[4, :, :5] = np.linspace(0.5 * spacing, 0.9 * spacing, 5) offset_distances[5, :, 5:] = np.linspace(0, 0.4 * spacing, 5) else: # -ve stagger offset_distances[4, :, :] = np.linspace(-0.5 * spacing, 0.4 * spacing, 10) data = get_data_inc_reciprocals(distances, spacing, 'x', 0, 0) offset_data = get_data_inc_reciprocals(offset_distances, spacing, 'x', offset, 0) dmask = np.full(21, True, dtype=bool) dmask[1] = False data = data[dmask] offset_data = offset_data[dmask] add_distance_column(data) add_distance_column(offset_data) if point_type == 'first': data = data[::2] data.dist = -order // 2 offset_data = offset_data[::2] offset_data.dist = -order // 2 # No need to drop points or shift grid endpoint, as that is done here else: data = data[1::2] data.dist = order // 2 offset_data = offset_data[1::2] offset_data.dist = order // 2 # No need to drop points or shift grid endpoint, as that is done here # Set n_pts data['n_pts'] = order // 2 offset_data['n_pts'] = order // 2 grid = Grid(shape=(10, 1, 10), extent=(9 * spacing, 0, 9 * spacing)) s_dim = Dimension(name='s') ncoeffs = order + 1 w_shape = grid.shape + (ncoeffs, ) w_dims = grid.dimensions + (s_dim, ) w_normal = Function(name='w_n', dimensions=w_dims, shape=w_shape) w_offset = Function(name='w_o', dimensions=w_dims, shape=w_shape) fill_stencils(data, point_type, max_ext_points, lambdas, w_normal, 10, 'x') fill_stencils(offset_data, point_type, max_ext_points, lambdas, w_offset, 10, 'x') if point_type == 'first': assert np.all(np.isclose(w_normal.data[2:5], w_offset.data[2:5])) else: assert np.all(np.isclose(w_normal.data[5:7], w_offset.data[5:7]))
def get_weights(data, function, deriv, bcs, interior, fill_function=None, eval_offsets=(0., 0., 0.)): """ Get the modified stencil weights for a function and derivative given the axial distances. Parameters ---------- data : devito VectorFunction The axial distance function function : devito Function The function for which stencils should be calculated deriv : int The order of the derivative to which the stencils pertain bcs : list of devito Eq The boundary conditions which should hold at the surface interior : ndarray The interior-exterior segmentation of the domain fill_function : devito Function A secondary function to use when creating the Coefficient objects. If none then the Function supplied with the function argument will be used instead. eval_offsets : tuple of float The relative offsets at which derivatives should be evaluated for each axis. Returns ------- substitutions : tuple of Coefficient The substitutions to be included in the devito equation """ cache = os.path.dirname(__file__) + '/extrapolation_cache.dat' # This wants to start as an empty list weights = [] for axis in range(3): print(deriv, function, function.grid.dimensions[axis]) stencils = StencilSet(deriv, eval_offsets[axis], bcs, cache=cache) lambdas = stencils.lambdaify max_span = stencils.max_span axis_weights = get_component_weights(data[axis].data, axis, function, deriv, lambdas, interior, max_span, eval_offsets[axis]) if fill_function is None: weights.append( Coefficient(deriv, function, function.grid.dimensions[axis], axis_weights)) else: weights.append( Coefficient(deriv, fill_function, fill_function.grid.dimensions[axis], axis_weights)) # Raise error if list is empty if len(weights) == 0: raise ValueError("No boundary-adjacent points in provided fields") return tuple(weights)
def test_derivative_recovery(self, setup): """ Test that appropriate polynomials and their derivatives are exactly recovered by the generated stencils. """ print(setup) # First need to create the stencils and get the lambdaified versions order = setup['order'] if setup['bcs'] == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) # Define benchmark function def fnc(x): return sum([ x**term if term % 2 != 0 else 0 for term in range(order + 1) ]) if setup['deriv'] == 1: def tru_drv(x): return sum([ term * x**(term - 1) if term % 2 != 0 else 0 for term in range(1, order + 1) ]) elif setup['deriv'] == 2: def tru_drv(x): return sum([ term * (term - 1) * x**(term - 2) if term % 2 != 0 else 0 for term in range(2, order + 1) ]) else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) # Define benchmark function def fnc(x): return sum([ x**term if term % 2 == 0 else 0 for term in range(order + 1) ]) if setup['deriv'] == 1: def tru_drv(x): return sum([ term * x**(term - 1) if term % 2 == 0 else 0 for term in range(1, order + 1) ]) elif setup['deriv'] == 2: def tru_drv(x): return sum([ term * (term - 1) * x**(term - 2) if term % 2 == 0 else 0 for term in range(2, order + 1) ]) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' stencils = StencilSet(setup['deriv'], setup['offset'], bcs, cache=cache) lambdas = stencils.lambdaify # Create a set of distances to test and a corresponding set of keys l_mod = setup['offset'] == 0.5 r_mod = setup['offset'] == -0.5 # TODO: Would be more rigorous if it used separate left and right distances distances = np.linspace(0.1 - order / 2 + l_mod, order / 2 - 0.1 - r_mod, 5 * order) if setup['offset'] == 0.5: max_l = 0.5 else: max_l = 0 if setup['offset'] == -0.5: min_r = -0.5 else: min_r = 0 l_dist = distances[distances < max_l] r_dist = distances[distances > min_r] l_len = l_dist.shape r_len = r_dist.shape l_dist = np.append(l_dist, np.full(r_len, np.NaN)) r_dist = np.append(np.full(l_len, np.NaN), r_dist) misfit = np.zeros(len(l_dist)) dx = 1 # Loop over lambda keys for key in lambdas: eta_l, eta_r = key if np.isnan(eta_l): mask = np.logical_and(r_dist >= eta_r, r_dist < eta_r + 0.5) elif np.isnan(eta_r): mask = np.logical_and(l_dist < eta_l, l_dist >= eta_l - 0.5) else: mask = np.full(l_dist.shape, False) key_l = l_dist[mask] key_r = r_dist[mask] key_s = np.zeros(key_l.shape) for index in lambdas[key]: if np.isnan(eta_l): key_s += lambdas[key][index](key_l, key_r) * fnc( (index - key_r) * dx) elif np.isnan(eta_r): key_s += lambdas[key][index](key_l, key_r) * fnc( (index - key_l) * dx) if np.isnan(eta_l): misfit[mask] = np.abs((key_s / dx**setup['deriv']) - tru_drv((setup['offset'] - key_r) * dx)) elif np.isnan(eta_r): misfit[mask] = np.abs((key_s / dx**setup['deriv']) - tru_drv((setup['offset'] - key_l) * dx)) assert np.median(misfit) < 5e-6
def test_stencil_convergence(self, setup): """ Check that grid refinement increases accuracy in line with discretization order. """ # First need to create the stencils and get the lambdaified versions order = setup['order'] if setup['bcs'] == 'even': bcs = BoundaryConditions({2 * i: 0 for i in range(1 + order // 2)}, order) # Define benchmark function def fnc(x): return np.sin(x) if setup['deriv'] == 1: def tru_drv(x): return np.cos(x) elif setup['deriv'] == 2: def tru_drv(x): return -np.sin(x) else: bcs = BoundaryConditions( {2 * i + 1: 0 for i in range(1 + order // 2)}, order) # Define benchmark function def fnc(x): return np.cos(x) if setup['deriv'] == 1: def tru_drv(x): return -np.sin(x) elif setup['deriv'] == 2: def tru_drv(x): return -np.cos(x) cache = os.path.dirname( __file__) + '/../devitoboundary/extrapolation_cache.dat' stencils = StencilSet(setup['deriv'], setup['offset'], bcs, cache=cache) lambdas = stencils.lambdaify # Create a set of distances to test and a corresponding set of keys l_mod = setup['offset'] == 0.5 r_mod = setup['offset'] == -0.5 # TODO: Would be more rigorous if it used separate left and right distances distances = np.linspace(0.1 - order / 2 + l_mod, order / 2 - 0.1 - r_mod, 5 * order) if setup['offset'] == 0.5: max_l = 0.5 else: max_l = 0 if setup['offset'] == -0.5: min_r = -0.5 else: min_r = 0 l_dist = distances[distances < max_l] r_dist = distances[distances > min_r] l_len = l_dist.shape r_len = r_dist.shape l_dist = np.append(l_dist, np.full(r_len, np.NaN)) r_dist = np.append(np.full(l_len, np.NaN), r_dist) # Create a set of spacings spacings = np.linspace(2, 0.2, 20) misfit = np.zeros((len(spacings), len(l_dist))) for i in range(len(spacings)): dx = spacings[i] # Loop over lambda keys for key in lambdas: eta_l, eta_r = key if np.isnan(eta_l): mask = np.logical_and(r_dist >= eta_r, r_dist < eta_r + 0.5) elif np.isnan(eta_r): mask = np.logical_and(l_dist < eta_l, l_dist >= eta_l - 0.5) else: mask = np.full(l_dist.shape, False) key_l = l_dist[mask] key_r = r_dist[mask] key_s = np.zeros(key_l.shape) for index in lambdas[key]: if np.isnan(eta_l): key_s += lambdas[key][index](key_l, key_r) * fnc( (index - key_r) * dx) elif np.isnan(eta_r): key_s += lambdas[key][index](key_l, key_r) * fnc( (index - key_l) * dx) if np.isnan(eta_l): misfit[i, mask] = np.abs((key_s / dx**setup['deriv']) - tru_drv((setup['offset'] - key_r) * dx)) elif np.isnan(eta_r): misfit[i, mask] = np.abs((key_s / dx**setup['deriv']) - tru_drv((setup['offset'] - key_l) * dx)) log_dx = np.log10(spacings) log_m = np.log10(misfit) convergence_gradients = np.polyfit(log_dx, log_m, 1)[0] assert np.mean(convergence_gradients) > order - 1