def main(): """main routine testing the performance""" print("Reports calls-per-second (larger is better)\n") # Cartesian grid with different shapes and boundary conditions for size in [32, 512]: grid = UnitGrid([size, size], periodic=False) print(grid) field = ScalarField.random_normal(grid) bc_value = np.ones(size) result = field.laplace(bc={"value": 1}).data for bc in ["scalar", "array", "function", "time-dependent", "linked"]: if bc == "scalar": bcs = {"value": 1} elif bc == "array": bcs = {"value": bc_value} elif bc == "function": bcs = grid.get_boundary_conditions( {"virtual_point": "2 - value"}) elif bc == "time-dependent": bcs = grid.get_boundary_conditions({"value_expression": "t"}) elif bc == "linked": bcs = grid.get_boundary_conditions({"value": bc_value}) for ax, upper in grid._iter_boundaries(): bcs[ax][upper].link_value(bc_value) else: raise RuntimeError # create the operator with these conditions laplace = grid.make_operator("laplace", bc=bcs) if bc == "time-dependent": args = numba_dict({"t": 1}) # call once to pre-compile and test result np.testing.assert_allclose(laplace(field.data, args=args), result) # estimate the speed speed = estimate_computation_speed(laplace, field.data, args=args) else: # call once to pre-compile and test result np.testing.assert_allclose(laplace(field.data), result) # estimate the speed speed = estimate_computation_speed(laplace, field.data) print(f"{bc:>14s}:{int(speed):>9d}") print()
def test_gradient_1d(): """ test specific boundary conditions for the 1d gradient """ grid = UnitGrid(5) b_l = {"type": "derivative", "value": -1} b_r = {"type": "derivative", "value": 1} bcs = grid.get_boundary_conditions([b_l, b_r]) grad = ops._make_gradient_numba_1d(bcs) np.testing.assert_allclose(grad(np.arange(5)), np.ones((1, 5))) b_l = {"type": "value", "value": 3} b_r = {"type": "value", "value": 3} bcs = grid.get_boundary_conditions([b_l, b_r]) grad = ops._make_gradient_numba_1d(bcs) np.testing.assert_allclose(grad(np.full(5, 3)), np.zeros((1, 5)))
def test_setting_specific_bcs(): """test the interface of setting specific conditions""" grid = UnitGrid([4, 4], periodic=[False, True]) bcs = grid.get_boundary_conditions("auto_periodic_neumann") # test non-periodic axis assert str(bcs[0]) == '"derivative"' assert str(bcs["x"]) == '"derivative"' bcs["x"] = "value" assert str(bcs["x"]) == '"value"' bcs["left"] = "derivative" assert str(bcs["left"]) == '"derivative"' assert str(bcs["right"]) == '"value"' bcs["right"] = "derivative" assert str(bcs["x"]) == '"derivative"' with pytest.raises(PeriodicityError): bcs["x"] = "periodic" # test periodic axis assert str(bcs[1]) == '"periodic"' assert str(bcs["y"]) == '"periodic"' assert str(bcs["top"]) == '"periodic"' bcs["y"] = "periodic" with pytest.raises(PeriodicityError): bcs["y"] = "value" with pytest.raises(PeriodicityError): bcs["top"] = "value" # test wrong input with pytest.raises(KeyError): bcs["nonsense"] with pytest.raises(TypeError): bcs[None] with pytest.raises(KeyError): bcs["nonsense"] = None
def test_user_bcs_numba(dim, target): """test setting user BCs""" if dim == 1: value = 1 elif dim == 2: value = np.arange(3) elif dim == 3: value = np.arange(9).reshape(3, 3) grid = UnitGrid([3] * dim) bcs = grid.get_boundary_conditions({"type": "user"}) # use normal method to set BCs f1 = ScalarField(grid) f1.set_ghost_cells(bc={target: value}) # use user method to set BCs f2 = ScalarField(grid) setter = bcs.make_ghost_cell_setter() # test whether normal call is a no-op f2._data_full = 3 setter(f2._data_full) np.testing.assert_allclose(f2._data_full, 3) setter(f2._data_full, args={"t": 1}) np.testing.assert_allclose(f2._data_full, 3) f2._data_full = 0 # test whether calling setter with user data works properly setter(f2._data_full, args={target: value}) np.testing.assert_allclose(f1._data_full, f2._data_full)
def test_user_bcs_numpy(dim, target): """test setting user BCs""" value = np.arange(3) if dim == 2 else 1 grid = UnitGrid([3] * dim) bcs = grid.get_boundary_conditions({"type": "user"}) # use normal method to set BCs f1 = ScalarField(grid) f1.set_ghost_cells(bc={target: value}) # use user method to set BCs f2 = ScalarField(grid) # test whether normal call is a no-op f2._data_full = 3 f2.set_ghost_cells(bc=bcs) np.testing.assert_allclose(f2._data_full, 3) f2.set_ghost_cells(bc=bcs, args={"t": 1}) np.testing.assert_allclose(f2._data_full, 3) f2._data_full = 0 # test whether calling setter with user data works properly bcs.set_ghost_cells(f2._data_full, args={target: value}) np.testing.assert_allclose(f1._data_full, f2._data_full)
def test_gradient_1d(): """test specific boundary conditions for the 1d gradient""" grid = UnitGrid(5) b_l = {"type": "derivative", "value": -1} b_r = {"type": "derivative", "value": 1} bcs = grid.get_boundary_conditions([b_l, b_r]) field = ScalarField(grid, np.arange(5)) res = field.gradient(bcs) np.testing.assert_allclose(res.data, np.ones((1, 5))) b_l = {"type": "value", "value": 3} b_r = {"type": "value", "value": 3} bcs = grid.get_boundary_conditions([b_l, b_r]) field = ScalarField(grid, np.full(5, 3)) res = field.gradient(bcs) np.testing.assert_allclose(res.data, np.zeros((1, 5)))
def test_bc_values(): """ test setting the values of boundary conditions """ g = UnitGrid([5]) bc = g.get_boundary_conditions([{"value": 2}, {"derivative": 3}]) assert bc[0].low.value == 2 and bc[0].high.value == 3 bc.scale_value(5) assert bc[0].low.value == 10 and bc[0].high.value == 15 bc.set_value(7) assert bc[0].low.value == bc[0].high.value == 7
def test_set_ghost_cells(dim, periodic): """test setting values for ghost cells""" grid = UnitGrid([1] * dim, periodic=periodic) field = ScalarField.random_uniform(grid) bcs = grid.get_boundary_conditions("natural") arr1 = field._data_all.copy() bcs.set_ghost_cells(arr1) arr2 = field._data_all.copy() setter = bcs.make_ghost_cell_setter() setter(arr2) # test valid BCs: for n in range(dim): idx = [slice(1, -1)] * dim idx[n] = slice(None) np.testing.assert_allclose(arr1[tuple(idx)], arr2[tuple(idx)])
def test_setting_boundary_conditions(): """ test setting some boundary conditions """ grid = UnitGrid([3, 3], periodic=[True, False]) for bc in [ grid.get_boundary_conditions("natural"), grid.get_boundary_conditions(["natural", "no-flux"]), ]: assert isinstance(bc, Boundaries) for bc in ["periodic", "value"]: with pytest.raises(PeriodicityError): grid.get_boundary_conditions(bc) grid = UnitGrid([2], periodic=True) with pytest.raises(PeriodicityError): grid.get_boundary_conditions("derivative") grid = UnitGrid([2], periodic=False) with pytest.raises(PeriodicityError): grid.get_boundary_conditions("periodic")
def test_boundaries_edge_cases(): """test treatment of invalid data""" grid = UnitGrid([3, 3]) bcs = grid.get_boundary_conditions("auto_periodic_neumann") with pytest.raises(BCDataError): Boundaries([]) with pytest.raises(BCDataError): Boundaries([bcs[0]]) with pytest.raises(BCDataError): Boundaries([bcs[0], bcs[0]]) assert bcs == Boundaries([bcs[0], bcs[1]]) bc0 = get_boundary_axis(grid.copy(), 0, "auto_periodic_neumann") assert bcs == Boundaries([bc0, bcs[1]]) bc0 = get_boundary_axis(UnitGrid([4, 3]), 0, "auto_periodic_neumann") with pytest.raises(BCDataError): Boundaries([bc0, bcs[1]]) bc0 = get_boundary_axis(UnitGrid([3, 3], periodic=True), 0, "auto_periodic_neumann") with pytest.raises(BCDataError): Boundaries([bc0, bcs[1]])
def test_setting_domain_rect(): """test various versions of settings bcs for Cartesian grids""" grid = UnitGrid([2, 2]) grid.get_boundary_conditions(["derivative", "derivative"]) # wrong number of conditions with pytest.raises(ValueError): grid.get_boundary_conditions(["derivative"]) with pytest.raises(ValueError): grid.get_boundary_conditions(["derivative"] * 3) grid = UnitGrid([2, 2], periodic=[True, False]) grid.get_boundary_conditions("auto_periodic_neumann") grid.get_boundary_conditions(["periodic", "derivative"]) # incompatible conditions with pytest.raises(RuntimeError): grid.get_boundary_conditions("periodic") with pytest.raises(RuntimeError): grid.get_boundary_conditions("derivative") with pytest.raises(RuntimeError): grid.get_boundary_conditions(["derivative", "periodic"])
def main(): """main routine testing the performance""" print("Reports calls-per-second (larger is better)") print(" The `CUSTOM` method implemented by hand is the baseline case.") print(" The `FLEXIBLE` method is a serial implementation using the " "boundary conditions from the package.") print(" The other methods use the functions supplied by the package.\n") # Cartesian grid with different shapes and boundary conditions for shape in [(32, 32), (512, 512)]: for periodic in [True, False]: grid = UnitGrid(shape, periodic=periodic) print(grid) field = ScalarField.random_normal(grid) bcs = grid.get_boundary_conditions("natural", rank=0) expected = field.laplace("natural") for method in [ "CUSTOM", "FLEXIBLE", "OPTIMIZED", "numba", "scipy" ]: if method == "CUSTOM": laplace = custom_laplace_2d(shape, periodic=periodic) elif method == "FLEXIBLE": laplace = flexible_laplace_2d(bcs) elif method == "OPTIMIZED": laplace = optimized_laplace_2d(bcs) elif method in {"numba", "scipy"}: laplace = grid.make_operator("laplace", bc=bcs, backend=method) else: raise ValueError(f"Unknown method `{method}`") # call once to pre-compile and test result if method == "OPTIMIZED": result = laplace(field._data_all) np.testing.assert_allclose(result, expected.data) speed = estimate_computation_speed(laplace, field._data_all) else: np.testing.assert_allclose(laplace(field.data), expected.data) speed = estimate_computation_speed(laplace, field.data) print(f"{method:>9s}: {int(speed):>9d}") print() # Cylindrical grid with different shapes for shape in [(32, 64), (512, 512)]: grid = CylindricalSymGrid(shape[0], [0, shape[1]], shape) print(f"Cylindrical grid, shape={shape}") field = ScalarField.random_normal(grid) bcs = Boundaries.from_data(grid, "derivative") expected = field.laplace(bcs) for method in ["CUSTOM", "numba"]: if method == "CUSTOM": laplace = custom_laplace_cyl_neumann(shape) elif method == "numba": laplace = grid.make_operator("laplace", bc=bcs) else: raise ValueError(f"Unknown method `{method}`") # call once to pre-compile and test result np.testing.assert_allclose(laplace(field.data), expected.data) speed = estimate_computation_speed(laplace, field.data) print(f"{method:>8s}: {int(speed):>9d}") print() # Spherical grid with different shapes for shape in [32, 512]: grid = SphericalSymGrid(shape, shape) print(grid) field = ScalarField.random_normal(grid) bcs = Boundaries.from_data(grid, "derivative") for conservative in [True, False]: laplace = grid.make_operator("laplace", bcs, conservative=conservative) laplace(field.data) # call once to pre-compile speed = estimate_computation_speed(laplace, field.data) print( f" numba (conservative={str(conservative):<5}): {int(speed):>9d}" ) print()
def test_setting_domain_rect(): """ test various versions of settings bcs for cartesian grids """ grid = UnitGrid([2, 2]) grid.get_boundary_conditions(["no-flux", "no-flux"]) # wrong number of conditions with pytest.raises(ValueError): grid.get_boundary_conditions(["no-flux"]) with pytest.raises(ValueError): grid.get_boundary_conditions(["no-flux"] * 3) grid = UnitGrid([2, 2], periodic=[True, False]) grid.get_boundary_conditions("natural") grid.get_boundary_conditions(["periodic", "no-flux"]) # incompatible conditions with pytest.raises(RuntimeError): grid.get_boundary_conditions("periodic") with pytest.raises(RuntimeError): grid.get_boundary_conditions("no-flux") with pytest.raises(RuntimeError): grid.get_boundary_conditions(["no-flux", "periodic"])