diff --git a/.pylintrc-local.yml b/.pylintrc-local.yml index be1948583b86a81c56b102f4c159e1282e5d31dd..08f36d4c97f33574ae9c40bded4b8fba226de85b 100644 --- a/.pylintrc-local.yml +++ b/.pylintrc-local.yml @@ -9,3 +9,6 @@ - pml.py - poisson.py - second_order.py +- arg: ignored-modules + val: + - sympy diff --git a/doc/utils.rst b/doc/utils.rst index 2235db8f518489069dc52b20683cbe60e838d457..a6f068b6f8ef033d4641f72b2810ce4889ac50e3 100644 --- a/doc/utils.rst +++ b/doc/utils.rst @@ -10,3 +10,8 @@ Array contexts -------------- .. automodule:: grudge.array_context + +Miscellaneous tools +------------------- + +.. automodule:: grudge.tools diff --git a/grudge/models/wave.py b/grudge/models/wave.py index 1cb1cdb4a2813f35c621adbea8b814f5d97879ce..209575ba3258f5cfcf983dc49b13f880ef2c8748 100644 --- a/grudge/models/wave.py +++ b/grudge/models/wave.py @@ -182,6 +182,9 @@ class WeakWaveOperator(HyperbolicOperator): def max_characteristic_velocity(self, actx, t=None, fields=None): return abs(self.c) + def estimate_rk4_timestep(self, actx, dcoll, **kwargs): + # FIXME: Sketchy, empirically determined fudge factor + return 0.38 * super().estimate_rk4_timestep(actx, dcoll, **kwargs) # }}} diff --git a/grudge/tools.py b/grudge/tools.py index 25018b530ccf1748f20c4a04191bcacf8e622799..9723f9249414c7b268c064d938944100991db2aa 100644 --- a/grudge/tools.py +++ b/grudge/tools.py @@ -1,4 +1,6 @@ -"""Miscellaneous helper facilities.""" +""" +.. autofunction:: build_jacobian +""" __copyright__ = "Copyright (C) 2007 Andreas Kloeckner" @@ -199,3 +201,38 @@ class OrderedSet(MutableSet): return set(self) == set(other) # }}} + + +def build_jacobian(actx, f, base_state, stepsize): + """Returns a Jacobian matrix of *f* determined by a one-sided finite + difference approximation with *stepsize*. + + :param f: a callable with a single argument, any array or + :class:`arraycontext.ArrayContainer` supported by *actx*. + :param base_state: The point at which the Jacobian is to be + calculated. May be any array or :class:`arraycontext.ArrayContainer` + supported by *actx*. + :returns: a two-dimensional :class:`numpy.ndarray` + """ + from arraycontext import flatten, unflatten + flat_base_state = flatten(base_state, actx) + + n, = flat_base_state.shape + + mat = np.empty((n, n), dtype=flat_base_state.dtype) + + f_base = f(base_state) + + for i in range(n): + unit_i_flat = np.zeros(n, dtype=mat.dtype) + unit_i_flat[i] = stepsize + + f_unit_i = f(f_base + unflatten( + base_state, actx.from_numpy(unit_i_flat), actx)) + + mat[:, i] = actx.to_numpy(flatten((f_unit_i - f_base) / stepsize, actx)) + + return mat + + +# vim: foldmethod=marker diff --git a/requirements.txt b/requirements.txt index abf6d39fa3d227e72056061b4fa08efa6d9044a5..2107e5aeb28297c113c8e4b92fcf7c11439df48a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,6 @@ git+https://github.com/inducer/pyvisfile.git#egg=pyvisfile git+https://github.com/inducer/pymetis.git#egg=pymetis git+https://github.com/illinois-ceesd/logpyle.git#egg=logpyle git+https://github.com/inducer/pytato.git#egg=pytato + +# for test_wave_dt_estimate +sympy diff --git a/test/test_dt_utils.py b/test/test_dt_utils.py index 573e7bad6c6c390000b929846ccfaf6fcdd23a9b..af9cbb6b4b88384d568ad6aa3158914cb7577fdf 100644 --- a/test/test_dt_utils.py +++ b/test/test_dt_utils.py @@ -45,6 +45,7 @@ import logging logger = logging.getLogger(__name__) +from meshmode import _acf # noqa: F401 @pytest.mark.parametrize("name", ["interval", "box2d", "box3d"]) @@ -123,6 +124,74 @@ def test_non_geometric_factors(actx_factory, name): assert all(factors <= upper_bounds) +@pytest.mark.parametrize("dim", [1, 2]) +@pytest.mark.parametrize("degree", [2, 4]) +def test_wave_dt_estimate(actx_factory, dim, degree, visualize=False): + actx = actx_factory() + + import meshmode.mesh.generation as mgen + + a = [0, 0, 0] + b = [1, 1, 1] + mesh = mgen.generate_regular_rect_mesh( + a=a[:dim], b=b[:dim], + nelements_per_axis=(3,)*dim) + assert mesh.dim == dim + + dcoll = DiscretizationCollection(actx, mesh, order=degree) + + from grudge.models.wave import WeakWaveOperator + wave_op = WeakWaveOperator(dcoll, c=1) + rhs = actx.compile( + lambda w: wave_op.operator(t=0, w=w)) + + from pytools.obj_array import make_obj_array + fields = make_obj_array([dcoll.zeros(actx) for i in range(dim+1)]) + + from grudge.tools import build_jacobian + mat = build_jacobian(actx, rhs, fields, 1) + + import numpy.linalg as la + eigvals = la.eigvals(mat) + + assert (eigvals.real <= 1e-12).all() + + from leap.rk import stability_function, RK4MethodBuilder + import sympy as sp + stab_func = sp.lambdify(*stability_function( + RK4MethodBuilder.a_explicit, + RK4MethodBuilder.output_coeffs)) + + dt_est = actx.to_numpy(wave_op.estimate_rk4_timestep(actx, dcoll)) + + if visualize: + re, im = np.mgrid[-4:1:30j, -5:5:30j] + sf_grid = np.abs(stab_func(re+1j*im)) + + import matplotlib.pyplot as plt + plt.contour(re, im, sf_grid, [0.25, 0.5, 0.75, 0.9, 1, 1.1]) + plt.colorbar() + plt.plot(dt_est * eigvals.real, dt_est * eigvals.imag, "x") + plt.grid() + plt.show() + + thresh = 1+1e-8 + max_stab = np.max(np.abs(stab_func(dt_est*eigvals))) + assert max_stab < thresh, max_stab + + dt_factors = 2**np.linspace(0, 4, 40)[1:] + stable_dt_factors = [ + dt_factor + for dt_factor in dt_factors + if np.max(np.abs(stab_func(dt_factor*dt_est*eigvals))) < thresh] + + if stable_dt_factors: + print(f"Stable timestep is {max(stable_dt_factors):.2f}x the estimate") + else: + print("Stable timestep estimate appears to be sharp") + assert not stable_dt_factors or max(stable_dt_factors) < 1.5, stable_dt_factors + + # You can test individual routines by typing # $ python test_grudge.py 'test_routine()' diff --git a/test/test_mpi_communication.py b/test/test_mpi_communication.py index 82a6cc234afdf1e0e0cccd8c182ca1274362c9ae..24e3d63805be26d573e0292c06109c9a2ddc6f95 100644 --- a/test/test_mpi_communication.py +++ b/test/test_mpi_communication.py @@ -243,9 +243,8 @@ def _test_mpi_wave_op_entrypoint(actx, visualize=False): [dcoll.zeros(actx) for i in range(dcoll.dim)] ) - # FIXME: Sketchy, empirically determined fudge factor dt = actx.to_numpy( - 0.45 * wave_op.estimate_rk4_timestep(actx, dcoll, fields=fields)) + wave_op.estimate_rk4_timestep(actx, dcoll, fields=fields)) wave_op.check_bc_coverage(local_mesh)