def test_compare_explicit(): """test explicit solvers""" grid = UnitGrid([16, 16]) field = ScalarField.random_uniform(grid, -1, 1) eq = DiffusionPDE() c1 = Controller(ExplicitSolver(eq), t_range=0.1, tracker=None) s1 = c1.run(field, dt=2e-3) c2 = Controller(ExplicitSolver(eq, scheme="runge-kutta"), t_range=0.1, tracker=None) with np.errstate(under="ignore"): s2 = c2.run(field, dt=2e-3) np.testing.assert_allclose(s1.data, s2.data, rtol=1e-2, atol=1e-2)
def test_storage_persistence(compression, tmp_path): """ test writing to persistent trackers """ dim = 5 grid = UnitGrid([dim]) field = ScalarField(grid) path = tmp_path / f"test_storage_persistence_{compression}.hdf5" # write some data for write_mode in ["append", "truncate_once", "truncate"]: storage = FileStorage(path, info={"a": 1}, write_mode=write_mode, compression=compression) # first batch storage.start_writing(field, info={"b": 2}) storage.append(field.copy(data=np.arange(dim)), 0) storage.append(field.copy(data=np.arange(dim, 2 * dim))) storage.end_writing() # read first batch np.testing.assert_array_equal(storage.times, np.arange(2)) np.testing.assert_array_equal(np.ravel(storage.data), np.arange(10)) assert {"a": 1, "b": 2}.items() <= storage.info.items() # second batch storage.start_writing(field, info={"c": 3}) storage.append(field.copy(data=np.arange(2 * dim, 3 * dim)), 2) storage.end_writing() storage.close() # read the data storage = FileStorage(path) if write_mode == "truncate": np.testing.assert_array_equal(storage.times, np.array([2])) np.testing.assert_array_equal(np.ravel(storage.data), np.arange(10, 15)) assert storage.shape == (1, 5) info = {"c": 3} assert info.items() <= storage.info.items() else: np.testing.assert_array_equal(storage.times, np.arange(0, 3)) np.testing.assert_array_equal(np.ravel(storage.data), np.arange(0, 15)) assert storage.shape == (3, 5) info = {"a": 1, "b": 2, "c": 3} assert info.items() <= storage.info.items()
def test_collections(): """test field collections""" grid = UnitGrid([3, 4]) sf = ScalarField.random_uniform(grid, label="sf") vf = VectorField.random_uniform(grid, label="vf") tf = Tensor2Field.random_uniform(grid, label="tf") fields = FieldCollection([sf, vf, tf]) assert fields.data.shape == (7, 3, 4) assert isinstance(str(fields), str) fields.data[:] = 0 np.testing.assert_allclose(sf.data, 0) np.testing.assert_allclose(vf.data, 0) np.testing.assert_allclose(tf.data, 0) assert fields[0] is fields["sf"] assert fields[1] is fields["vf"] assert fields[2] is fields["tf"] with pytest.raises(KeyError): fields["42"] sf.data = 1 vf.data = 1 tf.data = 1 np.testing.assert_allclose(fields.data, 1) assert all(np.allclose(i, 12) for i in fields.integrals) assert all(np.allclose(i, 1) for i in fields.averages) assert np.allclose(fields.magnitudes, np.sqrt([1, 2, 4])) assert sf.data.shape == (3, 4) assert vf.data.shape == (2, 3, 4) assert tf.data.shape == (2, 2, 3, 4) c2 = FieldBase.from_state(fields.attributes, data=fields.data) assert c2 == fields assert c2.grid is grid attrs = FieldCollection.unserialize_attributes( fields.attributes_serialized) c2 = FieldCollection.from_state(attrs, data=fields.data) assert c2 == fields assert c2.grid is not grid fields["sf"] = 2.0 np.testing.assert_allclose(sf.data, 2) with pytest.raises(KeyError): fields["42"] = 0 fields.plot(subplot_args=[{}, {"scale": 1}, {"colorbar": False}])
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_from_scalar_expressions(): """ test creating field collections from scalar expressions """ grid = UnitGrid([3]) expressions = ["x**2", "1"] fc = FieldCollection.from_scalar_expressions(grid, expressions=expressions, label="c", labels=["a", "b"]) assert fc.label == "c" assert fc[0].label == "a" assert fc[1].label == "b" assert fc[0].grid is grid assert fc[1].grid is grid np.testing.assert_allclose(fc[0].data, (np.arange(3) + 0.5)**2) np.testing.assert_allclose(fc[1].data, 1)
def test_collection_plotting(): """ test simple plotting of various fields on various grids """ grid = UnitGrid([5]) s1 = ScalarField(grid, label="s1") s2 = ScalarField(grid) fc = FieldCollection([s1, s2]) # test setting different figure sizes fc.plot(figsize="default") fc.plot(figsize="auto") fc.plot(figsize=(3, 3)) # test different arrangements fc.plot(arrangement="horizontal") fc.plot(arrangement="vertical")
def test_evaluate_func_vector(): """test the evaluate function with vector fields""" grid = UnitGrid([3]) field = ScalarField.from_expression(grid, "x") vec = VectorField.from_expression(grid, ["x"]) res = evaluate("inner(v, v)", {"v": vec}) assert isinstance(res, ScalarField) np.testing.assert_almost_equal(res.data, grid.axes_coords[0] ** 2) res = evaluate("outer(v, v)", {"v": vec}) assert isinstance(res, Tensor2Field) np.testing.assert_almost_equal(res.data, [[grid.axes_coords[0] ** 2]]) assert isinstance(evaluate("gradient(a)", {"a": field}), VectorField)
def test_diffusion_time_dependent_bcs(backend): """test PDE with time-dependent BCs""" field = ScalarField(UnitGrid([3])) eq = DiffusionPDE(bc={"value_expression": "Heaviside(t - 1.5)"}) storage = MemoryStorage() eq.solve(field, t_range=10, dt=1e-2, backend=backend, tracker=storage.tracker(1)) np.testing.assert_allclose(storage[1].data, 0) np.testing.assert_allclose(storage[-1].data, 1, rtol=1e-3)
def test_boundaries(): """test setting boundaries for multiple systems""" b = ["periodic", "value", {"type": "derivative", "value": 1}] for bx, by in itertools.product(b, b): g = UnitGrid([2, 2], periodic=[b == "periodic" for b in (bx, by)]) bcs = Boundaries.from_data(g, [bx, by]) bc_x = get_boundary_axis(g, 0, bx) bc_y = get_boundary_axis(g, 1, by) assert bcs.grid.num_axes == 2 assert bcs[0] == bc_x assert bcs[1] == bc_y assert bcs == Boundaries.from_data(g, [bc_x, bc_y]) if bx == by: assert bcs == Boundaries.from_data(g, bx) bc2 = bcs.copy() assert bcs == bc2 assert bcs is not bc2 b1 = Boundaries.from_data(UnitGrid([2, 2]), "natural") b2 = Boundaries.from_data(UnitGrid([3, 3]), "natural") assert b1 != b2
def test_unit_rect_grid(periodic): """test whether the rectangular grid behaves like a unit grid in special cases""" dim = random.randrange(1, 4) shape = np.random.randint(2, 10, size=dim) g1 = UnitGrid(shape, periodic=periodic) g2 = CartesianGrid(np.c_[np.zeros(dim), shape], shape, periodic=periodic) volume = np.prod(shape) for g in [g1, g2]: assert g.volume == pytest.approx(volume) assert g.integrate(1) == pytest.approx(volume) assert g.make_integrator()(np.ones(shape)) == pytest.approx(volume) assert g1.dim == g2.dim == dim np.testing.assert_array_equal(g1.shape, g2.shape) assert g1.typical_discretization == pytest.approx(g2.typical_discretization) for _ in range(10): p1, p2 = np.random.normal(scale=10, size=(2, dim)) assert g1.distance_real(p1, p2) == pytest.approx(g2.distance_real(p1, p2)) p0 = np.random.normal(scale=10, size=dim) np.testing.assert_allclose( g1.polar_coordinates_real(p0), g2.polar_coordinates_real(p0) )
def test_individual_boundaries(): """test setting individual boundaries""" g = UnitGrid([2]) for data in [ "value", { "value": 1 }, { "type": "value", "value": 1 }, "derivative", { "derivative": 1 }, { "type": "derivative", "value": 1 }, { "mixed": 1 }, { "type": "mixed", "value": 1 }, "extrapolate", ]: bc = BCBase.from_data(g, 0, upper=True, data=data, rank=0) assert isinstance(str(bc), str) assert isinstance(repr(bc), str) assert "field" in bc.get_mathematical_representation("field") assert bc.rank == 0 assert bc.homogeneous bc.check_value_rank(0) with pytest.raises(RuntimeError): bc.check_value_rank(1) for bc_copy in [ BCBase.from_data(g, 0, upper=True, data=bc, rank=0), bc.copy() ]: assert bc == bc_copy assert bc._cache_hash() == bc_copy._cache_hash() assert bc.extract_component() == bc
def test_solvers_simple_example(scheme, adaptive): """test explicit solvers""" grid = UnitGrid([4]) field = ScalarField(grid, 1) eq = PDE({"c": "c"}) dt = 1e-3 if scheme == "euler" else 1e-2 solver = ExplicitSolver(eq, scheme=scheme, adaptive=adaptive) controller = Controller(solver, t_range=10.0, tracker=None) res = controller.run(field, dt=dt) np.testing.assert_allclose(res.data, np.exp(10), rtol=0.1) if adaptive: assert solver.info["steps"] != pytest.approx(10 / dt, abs=1) else: assert solver.info["steps"] == pytest.approx(10 / dt, abs=1)
def test_outer_product(): """ test outer product of vector fields """ vf = VectorField(UnitGrid([1, 1]), [[[1]], [[2]]]) outer = vf.make_outer_prod_operator() tf = vf.outer_product(vf) res = np.array([1, 2, 2, 4]).reshape(2, 2, 1, 1) np.testing.assert_equal(tf.data, res) np.testing.assert_equal(outer(vf.data, vf.data), res) tf.data = 0 res = np.array([1, 2, 2, 4]).reshape(2, 2, 1, 1) vf.outer_product(vf, out=tf) np.testing.assert_equal(tf.data, res) outer(vf.data, vf.data, out=tf.data) np.testing.assert_equal(tf.data, res)
def test_pde_consistency_test(): """ test whether the consistency of a pde implementation is checked """ class TestPDE(pdes.PDEBase): def evolution_rate(self, field, t=0): return 2 * field def _make_pde_rhs_numba(self, state): def impl(state_data, t): return 3 * state_data return impl eq = TestPDE() state = ScalarField.random_uniform(UnitGrid([4])) with pytest.raises(AssertionError): eq.solve(state, t_range=5, tracker=None)
def test_compare_solvers(solver_class): """ compare several solvers """ field = ScalarField.random_uniform(UnitGrid([8, 8]), -1, 1) eq = DiffusionPDE() # ground truth c1 = Controller(ExplicitSolver(eq, scheme="runge-kutta"), t_range=0.1, tracker=None) s1 = c1.run(field, dt=5e-3) c2 = Controller(solver_class(eq), t_range=0.1, tracker=None) with np.errstate(under="ignore"): s2 = c2.run(field, dt=5e-3) np.testing.assert_allclose(s1.data, s2.data, rtol=1e-2, atol=1e-2)
def test_plot_tracker(tmp_path): """ test whether the plot tracker creates files without errors """ output_file = tmp_path / "img.png" def get_title(state, t): return f"{state.integral:g} at {t:g}" grid = UnitGrid([4, 4]) state = ScalarField.random_uniform(grid) pde = DiffusionPDE() tracker = trackers.PlotTracker( output_file=output_file, title=get_title, interval=0.1, show=False ) pde.solve(state, t_range=0.5, dt=0.005, tracker=tracker, backend="numpy") assert output_file.stat().st_size > 0
def test_data_tracker(tmp_path): """ test the DataTracker """ field = ScalarField(UnitGrid([4, 4])) eq = DiffusionPDE() path = tmp_path / "test_data_tracker.pickle" data1 = trackers.DataTracker(lambda f: f.average, filename=path) data2 = trackers.DataTracker(lambda f: {"avg": f.average, "int": f.integral}) eq.solve(field, 10, tracker=[data1, data2]) with path.open("br") as fp: time, data = pickle.load(fp) np.testing.assert_allclose(time, np.arange(11)) assert isinstance(data, list) assert len(data) == 11 assert path.stat().st_size > 0
def test_boundary_specifications(): """test different ways of specifying boundary conditions""" g = UnitGrid([2]) bc1 = Boundaries.from_data(g, [{ "type": "derivative", "value": 0 }, { "type": "value", "value": 0 }]) assert bc1 == Boundaries.from_data(g, [{ "type": "derivative" }, { "type": "value" }]) assert bc1 == Boundaries.from_data(g, [{"derivative": 0}, {"value": 0}]) assert bc1 == Boundaries.from_data(g, ["neumann", "dirichlet"])
def test_smoothing_collection(): """ test smoothing of a FieldCollection """ grid = UnitGrid([3, 4], periodic=[True, False]) sf = ScalarField.random_uniform(grid) vf = VectorField.random_uniform(grid) tf = Tensor2Field.random_uniform(grid) fields = FieldCollection([sf, vf, tf]) sgm = 0.5 + np.random.random() out = fields.smooth(sigma=sgm) for i in range(3): np.testing.assert_allclose(out[i].data, fields[i].smooth(sgm).data) out.data = 0 fields.smooth(sigma=sgm, out=out) for i in range(3): np.testing.assert_allclose(out[i].data, fields[i].smooth(sgm).data)
def test_solvers_complex(backend): """test solvers with a complex PDE""" r = FieldCollection.scalar_random_uniform(2, UnitGrid([3]), labels=["a", "b"]) c = r["a"] + 1j * r["b"] assert c.is_complex # assume c = a + i * b eq_c = PDE({"c": "-I * laplace(c)"}) eq_r = PDE({"a": "laplace(b)", "b": "-laplace(a)"}) res_r = eq_r.solve(r, t_range=1e-2, dt=1e-3, backend="numpy", tracker=None) exp_c = res_r[0].data + 1j * res_r[1].data for solver_class in [ExplicitSolver, ImplicitSolver, ScipySolver]: solver = solver_class(eq_c, backend=backend) controller = Controller(solver, t_range=1e-2, tracker=None) res_c = controller.run(c, dt=1e-3) np.testing.assert_allclose(res_c.data, exp_c, rtol=1e-3, atol=1e-3)
def test_virtual_points_linked_data(upper): """ test the calculation of virtual points with linked_data """ g = UnitGrid([2, 2]) point = (1, 1) if upper else (0, 0) data = np.zeros(g.shape) # test constant boundary conditions bc_data = np.array([1, 1]) bc = BCBase.from_data(g, 0, upper, {"type": "value", "value": bc_data}) assert not bc.value_is_linked bc.link_value(bc_data) assert bc.value_is_linked bc_data[:] = 3 assert bc.get_virtual_point(data, point) == pytest.approx(6) ev = bc.make_virtual_point_evaluator() assert ev(data, point) == pytest.approx(6) # test derivative boundary conditions (wrt to outwards derivative) bc = BCBase.from_data(g, 0, upper, { "type": "derivative", "value": bc_data }) assert not bc.value_is_linked bc.link_value(bc_data) assert bc.value_is_linked bc_data[:] = 4 assert bc.get_virtual_point(data, point) == pytest.approx(4) ev = bc.make_virtual_point_evaluator() assert ev(data, point) == pytest.approx(4) # test derivative boundary conditions (wrt to outwards derivative) bc = BCBase.from_data(g, 0, upper, { "type": "mixed", "value": bc_data, "const": 3 }) assert not bc.value_is_linked bc.link_value(bc_data) assert bc.value_is_linked bc_data[:] = 4 assert bc.get_virtual_point(data, point) == pytest.approx(1) ev = bc.make_virtual_point_evaluator() assert ev(data, point) == pytest.approx(1)
def test_stochastic_solvers(backend): """ test simple version of the stochastic solver """ field = ScalarField.random_uniform(UnitGrid([16]), -1, 1) eq = DiffusionPDE() seq = DiffusionPDE(noise=1e-6) solver1 = ExplicitSolver(eq, backend=backend) c1 = Controller(solver1, t_range=1, tracker=None) s1 = c1.run(field, dt=1e-3) solver2 = ExplicitSolver(seq, backend=backend) c2 = Controller(solver2, t_range=1, tracker=None) s2 = c2.run(field, dt=1e-3) np.testing.assert_allclose(s1.data, s2.data, rtol=1e-4, atol=1e-4) assert not solver1.info["stochastic"] assert solver2.info["stochastic"]
def test_material_conservation_tracker(): """ test the MaterialConservationTracker """ state = ScalarField.random_uniform(UnitGrid([8, 8]), 0, 1) solver = ExplicitSolver(CahnHilliardPDE()) controller = Controller(solver, t_range=1, tracker=["material_conservation"]) controller.run(state, dt=1e-3) assert controller.info["t_final"] >= 1 solver = ExplicitSolver(AllenCahnPDE()) controller = Controller(solver, t_range=1, tracker=["material_conservation"]) controller.run(state, dt=1e-3) assert controller.info["t_final"] <= 1
def test_from_expressions(): """ test initializing vector fields with expressions """ grid = UnitGrid([4, 4]) vf = VectorField.from_expression(grid, ["x**2", "x * y"]) xs = grid.cell_coords[..., 0] ys = grid.cell_coords[..., 1] np.testing.assert_allclose(vf.data[0], xs**2) np.testing.assert_allclose(vf.data[1], xs * ys) # corner case vf = VectorField.from_expression(grid, ["1", "x * y"]) with pytest.raises(ValueError): VectorField.from_expression(grid, "xy") with pytest.raises(ValueError): VectorField.from_expression(grid, ["xy"]) with pytest.raises(ValueError): VectorField.from_expression(grid, ["x"] * 3)
def test_wave_consistency(dim): """test some methods of the wave model""" eq = WavePDE() assert isinstance(str(eq), str) assert isinstance(repr(eq), str) # compare numba to numpy implementation grid = UnitGrid([4] * dim) state = eq.get_initial_condition(ScalarField.random_uniform(grid)) field = eq.evolution_rate(state) assert field.grid == grid rhs = eq._make_pde_rhs_numba(state) np.testing.assert_allclose(field.data, rhs(state.data, 0)) # compare to generic implementation assert isinstance(eq.expressions, dict) eq2 = PDE(eq.expressions) np.testing.assert_allclose(field.data, eq2.evolution_rate(state).data)
def test_emulsion_tracker(tmp_path): """test using the emulsions tracker""" path = tmp_path / "test_emulsion_tracker.hdf5" d = SphericalDroplet([4, 4], 3) c = d.get_phase_field(UnitGrid([8, 8])) pde = CahnHilliardPDE() e1 = EmulsionTimeCourse() tracker = e1.tracker(filename=path) pde.solve(c, t_range=1, dt=1e-3, backend="numpy", tracker=tracker) e2 = EmulsionTimeCourse.from_file(path, progress=False) assert e1 == e2 assert len(e1) == 2 assert len(e1[0]) == 1 # found a single droplet assert path.stat().st_size > 0 # wrote some result
def test_pde_consistency(pde_class, dim): """ test some methods of generic PDE models """ eq = pde_class() assert isinstance(str(eq), str) assert isinstance(repr(eq), str) # compare numba to numpy implementation grid = UnitGrid([4] * dim) state = ScalarField.random_uniform(grid) field = eq.evolution_rate(state) assert field.grid == grid rhs = eq._make_pde_rhs_numba(state) np.testing.assert_allclose(field.data, rhs(state.data, 0)) # compare to generic implementation assert isinstance(eq.expression, str) eq2 = pdes.PDE({"c": eq.expression}) np.testing.assert_allclose(field.data, eq2.evolution_rate(state).data)
def test_field_labels(): """ test the FieldCollection.labels property """ grid = UnitGrid([5]) s1 = ScalarField(grid, label="s1") s2 = ScalarField(grid) fc = FieldCollection([s1, s2]) assert fc.labels == ["s1", None] assert len(fc.labels) == 2 assert fc.labels[0] == "s1" assert fc.labels.index("s1") == 0 assert fc.labels.index(None) == 1 with pytest.raises(ValueError): fc.labels.index("a") fc.labels = ["a", "b"] assert fc.labels == ["a", "b"] fc.labels[0] = "c" assert fc.labels == ["c", "b"] assert str(fc.labels) == str(["c", "b"]) assert repr(fc.labels) == repr(["c", "b"]) assert fc.labels[0:1] == ["c"] assert fc.labels[:] == ["c", "b"] fc.labels[0:1] = "d" assert fc.labels == ["d", "b"] fc.labels[:] = "a" assert fc.labels == ["a", "a"] labels = fc.labels[:] labels[0] = "e" assert fc.labels == ["a", "a"] fc = FieldCollection([s1, s2], labels=[None, "b"]) assert fc.labels == [None, "b"] fc = FieldCollection([s1, s2], labels=["a", "b"]) assert fc.labels == ["a", "b"] with pytest.raises(TypeError): fc.labels = [1, "b"] with pytest.raises(TypeError): fc.labels[0] = 1
def test_appending(tmp_path): """test the appending data""" path = tmp_path / "test_appending.hdf5" c = ScalarField(UnitGrid([2]), data=1) storage = FileStorage(path) storage.start_writing(c) assert len(storage) == 0 storage.append(c, 0) assert storage._file_state == "writing" assert len(storage) == 1 storage.close() storage2 = FileStorage(path, write_mode="append") storage2.start_writing(c) storage2.append(c, 1) storage2.close() assert len(storage2) == 2
def test_from_expressions(): """test initializing tensor fields with expressions""" grid = UnitGrid([4, 4]) tf = Tensor2Field.from_expression(grid, [[1, 1], ["x**2", "x * y"]]) xs = grid.cell_coords[..., 0] ys = grid.cell_coords[..., 1] np.testing.assert_allclose(tf.data[0, 1], 1) np.testing.assert_allclose(tf.data[0, 1], 1) np.testing.assert_allclose(tf.data[1, 0], xs**2) np.testing.assert_allclose(tf.data[1, 1], xs * ys) # corner case with pytest.raises(ValueError): Tensor2Field.from_expression(grid, "xy") with pytest.raises(ValueError): Tensor2Field.from_expression(grid, ["xy"]) with pytest.raises(ValueError): Tensor2Field.from_expression(grid, ["x"] * 3) with pytest.raises(ValueError): Tensor2Field.from_expression(grid, [["x"], [1, 1]])