def get_performance_data(periodic=False): """obtain the data used in the performance plot Args: periodic (bool): The boundary conditions of the underlying grid Returns: dict: The durations of calculating the Laplacian on different grids using different methods """ sizes = 2**np.arange(3, 13) statistics = {} for size in display_progress(sizes): data = {} grid = UnitGrid([size] * 2, periodic=periodic) field = ScalarField.random_normal(grid) field.set_ghost_cells(bc="auto_periodic_neumann") for backend in ["numba", "scipy"]: op = grid.make_operator("laplace", bc="auto_periodic_neumann", backend=backend) data[backend] = time_function(op, field.data) op = grid.make_operator_no_bc("laplace", backend="numba") data["numba_no_bc"] = time_function(op, field._data_full, use_out=True) if opencv_laplace: data["opencv"] = time_function(opencv_laplace, field.data) statistics[int(size)] = data return statistics
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", "linked"]: if bc == "scalar": bcs = {"value": 1} elif bc == "array": bcs = {"value": bc_value} elif bc == "linked": bcs = Boundaries.from_data(grid, {"value": bc_value}, rank=0) for ax, upper in grid._iter_boundaries(): bcs[ax][upper].link_value(bc_value) # result = field.laplace(bc=bcs).data laplace = grid.get_operator("laplace", bc=bcs) # call once to pre-compile and test result np.testing.assert_allclose(laplace(field.data), result) speed = estimate_computation_speed(laplace, field.data) print(f"{bc:>6s}:{int(speed):>9d}") print()
def test_pde_bcs_error(bc): """test PDE with wrong boundary conditions""" eq = PDE({"u": "laplace(u)"}, bc=bc) grid = grids.UnitGrid([8, 8]) field = ScalarField.random_normal(grid) for backend in ["numpy", "numba"]: with pytest.raises(BCDataError): eq.solve(field, t_range=1, dt=0.01, backend=backend, tracker=None)
def test_pde_scalar(): """test PDE with a single scalar field""" eq = PDE({"u": "laplace(u) + exp(-t) + sin(t)"}) assert eq.explicit_time_dependence assert not eq.complex_valued grid = grids.UnitGrid([8]) field = ScalarField.random_normal(grid) res_a = eq.solve(field, t_range=1, dt=0.01, backend="numpy", tracker=None) res_b = eq.solve(field, t_range=1, dt=0.01, backend="numba", tracker=None) res_a.assert_field_compatible(res_b) np.testing.assert_allclose(res_a.data, res_b.data)
def test_pde_bcs(bc): """test PDE with boundary conditions""" eq = PDE({"u": "laplace(u)"}, bc=bc) assert not eq.explicit_time_dependence assert not eq.complex_valued grid = grids.UnitGrid([8]) field = ScalarField.random_normal(grid) res_a = eq.solve(field, t_range=1, dt=0.01, backend="numpy", tracker=None) res_b = eq.solve(field, t_range=1, dt=0.01, backend="numba", tracker=None) res_a.assert_field_compatible(res_b) np.testing.assert_allclose(res_a.data, res_b.data)
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_custom_operators(): """ test using a custom operator """ grid = grids.UnitGrid([32]) field = ScalarField.random_normal(grid) eq = PDE({"u": "undefined(u)"}) with pytest.raises(NameError): eq.evolution_rate(field) def make_op(state): return lambda state: state grids.UnitGrid.register_operator("undefined", make_op) eq._cache = {} # reset cache res = eq.evolution_rate(field) np.testing.assert_allclose(field.data, res.data) del grids.UnitGrid._operators["undefined"] # reset original state
""" Visualizing a 3d field interactively ==================================== This example demonstrates how to display 3d data interactively using the `napari <https://napari.org>`_ viewer. """ import numpy as np from pde import CartesianGrid, ScalarField # create a scalar field with some noise grid = CartesianGrid([[0, 2 * np.pi]] * 3, 64) data = ScalarField.from_expression( grid, "(cos(2 * x) * sin(3 * y) + cos(2 * z))**2") data += ScalarField.random_normal(grid, std=0.01) data.label = "3D Field" data.plot_interactive()
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()
""" Visualizing a scalar field ========================== This example displays methods for visualizing scalar fields. """ import numpy as np import matplotlib.pyplot as plt from pde import CylindricalGrid, ScalarField # create a scalar field with some noise grid = CylindricalGrid(7, [0, 4 * np.pi], 64) data = ScalarField.from_expression(grid, 'sin(z) * exp(-r / 3)') data += 0.05 * ScalarField.random_normal(grid) # manipulate the field smoothed = data.smooth() # Gaussian smoothing to get rid of the noise projected = data.project('r') # integrate along the radial direction sliced = smoothed.slice({'z': 1}) # slice the smoothed data # create four plots of the field and the modifications fig, axes = plt.subplots(nrows=2, ncols=2) data.plot(ax=axes[0, 0], title='Original field') smoothed.plot(ax=axes[1, 0], title='Smoothed field') projected.plot(ax=axes[0, 1], title='Projection on axial coordinate') sliced.plot(ax=axes[1, 1], title='Slice of smoothed field at $z=1$') plt.subplots_adjust(hspace=0.8)
def get_initial_state(self, grid): """ prepare a useful initial state """ u = ScalarField(grid, self.a, label="Field $u$") v = self.b / self.a + 0.1 * ScalarField.random_normal(grid, label="Field $v$") return FieldCollection([u, v])
Here, :math:`D_0` and :math:`D_1` are the respective diffusivity and the parameters :math:`a` and :math:`b` are related to reaction rates. Note that the same result can also be achieved with a :doc:`full implementation of a custom class <pde_brusselator_class>`, which allows for more flexibility at the cost of code complexity. """ from pde import PDE, FieldCollection, PlotTracker, ScalarField, UnitGrid # define the PDE a, b = 1, 3 d0, d1 = 1, 0.1 eq = PDE( { "u": f"{d0} * laplace(u) + {a} - ({b} + 1) * u + u**2 * v", "v": f"{d1} * laplace(v) + {b} * u - u**2 * v", } ) # initialize state grid = UnitGrid([64, 64]) u = ScalarField(grid, a, label="Field $u$") v = b / a + 0.1 * ScalarField.random_normal(grid, label="Field $v$") state = FieldCollection([u, v]) # simulate the pde tracker = PlotTracker(interval=1, plot_args={"vmin": 0, "vmax": 5}) sol = eq.solve(state, t_range=20, dt=1e-3, tracker=tracker)