From 8a7eb26be22fc479e7935294ded44bfe68258730 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Wed, 14 Feb 2024 15:45:45 -0600 Subject: [PATCH 1/2] Bump CI component versions (#998) --- .github/workflows/ci.yaml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 13a057f5c..e2b060c5d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -18,9 +18,9 @@ jobs: name: Flake8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: # matches compat target in setup.py python-version: '3.8' @@ -36,9 +36,9 @@ jobs: name: Mypy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install @@ -55,9 +55,9 @@ jobs: name: Pylint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install @@ -73,9 +73,9 @@ jobs: pydocstyle: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Run Pydocstyle @@ -91,7 +91,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install run: | . .ci-support/install.sh @@ -112,7 +112,7 @@ jobs: os: [ubuntu-latest, porter] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install run: | . .ci-support/install.sh @@ -127,7 +127,7 @@ jobs: # Temporary workaround for https://github.com/conda-forge/openvino-feedstock/pull/73 [[ $(hostname) == "porter" ]] && conda uninstall --yes ocl-icd-system - + [[ $(hostname) == "porter" ]] && export PYOPENCL_TEST="port:nv" && unset XDG_CACHE_HOME # This is only possible because actions run sequentially on porter @@ -140,7 +140,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install run: | . .ci-support/install.sh @@ -172,7 +172,7 @@ jobs: os: [ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install emirge run: | [[ $(uname) == Linux ]] && sudo apt-get update && sudo apt-get install -y openmpi-bin libopenmpi-dev @@ -198,7 +198,7 @@ jobs: os: [ubuntu-latest, macos-latest, porter] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' - name: Prepare production environment @@ -226,8 +226,8 @@ jobs: - name: Run production test run: | source ../config/activate_env.sh - + # Temporary workaround for https://github.com/conda-forge/openvino-feedstock/pull/73 [[ $(hostname) == "porter" ]] && conda uninstall --yes ocl-icd-system - + scripts/run-integrated-tests.sh --production From 3466be32c7cdd59de88b77af7b9795db401d90d5 Mon Sep 17 00:00:00 2001 From: Mike Campbell Date: Thu, 15 Feb 2024 11:14:46 -0600 Subject: [PATCH 2/2] Add options for TPE and example, grad test. (#997) --- examples/combozzle.py | 11 +- examples/pulse-tpe.py | 379 +++++++++++++++++++++++++++++++++++++ mirgecom/discretization.py | 35 ++-- mirgecom/simutil.py | 8 +- test/test_operators.py | 19 +- 5 files changed, 432 insertions(+), 20 deletions(-) create mode 100644 examples/pulse-tpe.py diff --git a/examples/combozzle.py b/examples/combozzle.py index 61e8338c5..0a2744ea2 100644 --- a/examples/combozzle.py +++ b/examples/combozzle.py @@ -143,7 +143,7 @@ def __call__(self, x_vec, *, time=0.0): @mpi_entry_point -def main(actx_class, rst_filename=None, +def main(actx_class, rst_filename=None, use_tpe=False, use_overintegration=False, casename=None, log_dependent=False, input_file=None, force_eval=True, use_esdg=False): @@ -602,7 +602,7 @@ def main(actx_class, rst_filename=None, rst_order = restart_data["order"] else: # generate the grid from scratch generate_mesh = partial(get_box_mesh, dim, a=box_ll, b=box_ur, n=nels_axis, - periodic=periodic) + periodic=periodic, tensor_product_elements=use_tpe) local_mesh, global_nelements = generate_and_distribute_mesh(comm, generate_mesh) @@ -612,7 +612,8 @@ def main(actx_class, rst_filename=None, if grid_only: return 0 - dcoll = create_discretization_collection(actx, local_mesh, order) + dcoll = create_discretization_collection(actx, local_mesh, order, + tensor_product_elements=use_tpe) nodes = actx.thaw(dcoll.nodes()) ones = dcoll.zeros(actx) + 1.0 @@ -1251,6 +1252,8 @@ def dummy_rhs(t, state): help="use numpy-based eager actx.") parser.add_argument("--restart_file", help="root name of restart file") parser.add_argument("--casename", help="casename to use for i/o") + parser.add_argument("--tpe", action="store_true", + help="Use tensor product elements (quads/hexes).") args = parser.parse_args() from warnings import warn @@ -1287,7 +1290,7 @@ def dummy_rhs(t, state): main(actx_class, input_file=input_file, use_overintegration=args.overintegration or args.esdg, - casename=casename, rst_filename=rst_filename, + casename=casename, rst_filename=rst_filename, use_tpe=args.tpe, log_dependent=log_dependent, force_eval=force_eval, use_esdg=args.esdg) # vim: foldmethod=marker diff --git a/examples/pulse-tpe.py b/examples/pulse-tpe.py new file mode 100644 index 000000000..a05eab1c7 --- /dev/null +++ b/examples/pulse-tpe.py @@ -0,0 +1,379 @@ +"""Demonstrate acoustic pulse using TPE, and adiabatic slip wall.""" + +__copyright__ = """ +Copyright (C) 2020 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import logging +import numpy as np +from functools import partial + +from meshmode.mesh import BTAG_ALL +from grudge.shortcuts import make_visualizer +from grudge.dof_desc import DISCR_TAG_QUAD + +from logpyle import IntervalTimer, set_dt + +from mirgecom.mpi import mpi_entry_point +from mirgecom.discretization import create_discretization_collection +from mirgecom.euler import euler_operator +from mirgecom.simutil import ( + get_sim_timestep, + distribute_mesh +) +from mirgecom.io import make_init_message + +from mirgecom.integrators import rk4_step +from mirgecom.steppers import advance_state +from mirgecom.boundary import AdiabaticSlipBoundary +from mirgecom.initializers import ( + Uniform, + AcousticPulse +) +from mirgecom.eos import IdealSingleGas +from mirgecom.gas_model import ( + GasModel, + make_fluid_state +) +from mirgecom.euler import extract_vars_for_logging, units_for_logging +from mirgecom.logging_quantities import ( + initialize_logmgr, + logmgr_add_many_discretization_quantities, + logmgr_add_cl_device_info, + logmgr_add_device_memory_usage, + set_sim_state +) + +logger = logging.getLogger(__name__) + + +class MyRuntimeError(RuntimeError): + """Simple exception to kill the simulation.""" + + pass + + +@mpi_entry_point +def main(actx_class, use_esdg=False, use_tpe=True, + use_overintegration=False, use_leap=False, + casename=None, rst_filename=None): + """Drive the example.""" + if casename is None: + casename = "mirgecom" + + from mpi4py import MPI + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + num_parts = comm.Get_size() + + from mirgecom.simutil import global_reduce as _global_reduce + global_reduce = partial(_global_reduce, comm=comm) + + logmgr = initialize_logmgr(True, + filename=f"{casename}.sqlite", mode="wu", mpi_comm=comm) + + from mirgecom.array_context import initialize_actx, actx_class_is_profiling + actx = initialize_actx(actx_class, comm) + queue = getattr(actx, "queue", None) + use_profiling = actx_class_is_profiling(actx_class) + + # timestepping control + current_step = 0 + if use_leap: + from leap.rk import RK4MethodBuilder + timestepper = RK4MethodBuilder("state") + else: + timestepper = rk4_step + t_final = 1.0 + current_cfl = 1.0 + current_dt = .005 + current_t = 0 + constant_cfl = False + + # some i/o frequencies + nstatus = 1 + nrestart = 5 + nviz = 100 + nhealth = 1 + + dim = 2 + rst_path = "restart_data/" + rst_pattern = ( + rst_path + "{cname}-{step:04d}-{rank:04d}.pkl" + ) + if rst_filename: # read the grid from restart data + rst_filename = f"{rst_filename}-{rank:04d}.pkl" + from mirgecom.restart import read_restart_data + restart_data = read_restart_data(actx, rst_filename) + local_mesh = restart_data["local_mesh"] + local_nelements = local_mesh.nelements + global_nelements = restart_data["global_nelements"] + assert restart_data["num_parts"] == num_parts + else: # generate the grid from scratch + from meshmode.mesh.generation import generate_regular_rect_mesh + from meshmode.mesh import TensorProductElementGroup + grp_cls = TensorProductElementGroup if use_tpe else None + box_ll = -1 + box_ur = 1 + nel_1d = 16 + generate_mesh = partial(generate_regular_rect_mesh, + a=(box_ll,)*dim, b=(box_ur,)*dim, + nelements_per_axis=(nel_1d,)*dim, + group_cls=grp_cls, + # periodic=(True,)*dim + ) + + local_mesh, global_nelements = distribute_mesh(comm, generate_mesh) + local_nelements = local_mesh.nelements + + order = 1 + dcoll = create_discretization_collection(actx, local_mesh, order=order, + tensor_product_elements=use_tpe) + nodes = actx.thaw(dcoll.nodes()) + quadrature_tag = DISCR_TAG_QUAD if use_overintegration else None + + vis_timer = None + + if logmgr: + logmgr_add_cl_device_info(logmgr, queue) + logmgr_add_device_memory_usage(logmgr, queue) + logmgr_add_many_discretization_quantities(logmgr, dcoll, dim, + extract_vars_for_logging, units_for_logging) + + vis_timer = IntervalTimer("t_vis", "Time spent visualizing") + logmgr.add_quantity(vis_timer) + + logmgr.add_watches([ + ("step.max", "step = {value}, "), + ("t_sim.max", "sim time: {value:1.6e} s\n"), + ("min_pressure", "------- P (min, max) (Pa) = ({value:1.9e}, "), + ("max_pressure", "{value:1.9e})\n"), + ("t_step.max", "------- step walltime: {value:6g} s, "), + ("t_log.max", "log walltime: {value:6g} s") + ]) + + eos = IdealSingleGas(gamma=1.4, gas_const=1.0) + gas_model = GasModel(eos=eos) + velocity = np.zeros(shape=(dim,)) + velocity[0] = 0.0 + orig = np.zeros(shape=(dim,)) + initializer = Uniform(velocity=velocity, pressure=1.0, rho=1.0) + uniform_state = initializer(nodes, eos=eos) + + boundaries = {BTAG_ALL: AdiabaticSlipBoundary()} + # boundaries = {} + + acoustic_pulse = AcousticPulse(dim=dim, amplitude=0.5, width=.1, center=orig) + + if rst_filename: + current_t = restart_data["t"] + current_step = restart_data["step"] + current_cv = restart_data["cv"] + else: + # Set the current state from time 0 + current_cv = acoustic_pulse(x_vec=nodes, cv=uniform_state, eos=eos) + + current_state = make_fluid_state(current_cv, gas_model) + + if logmgr: + from mirgecom.logging_quantities import logmgr_set_time + logmgr_set_time(logmgr, current_step, current_t) + + visualizer = make_visualizer(dcoll) + + initname = "pulse" + eosname = eos.__class__.__name__ + init_message = make_init_message(dim=dim, order=order, + nelements=local_nelements, + global_nelements=global_nelements, + dt=current_dt, t_final=t_final, nstatus=nstatus, + nviz=nviz, cfl=current_cfl, + constant_cfl=constant_cfl, initname=initname, + eosname=eosname, casename=casename) + if rank == 0: + logger.info(init_message) + + def my_write_viz(step, t, state, dv=None): + if dv is None: + dv = eos.dependent_vars(state) + viz_fields = [("cv", state), + ("dv", dv)] + from mirgecom.simutil import write_visfile + write_visfile(dcoll, viz_fields, visualizer, vizname=casename, + step=step, t=t, overwrite=True, vis_timer=vis_timer, + comm=comm) + + def my_write_restart(step, t, state): + rst_fname = rst_pattern.format(cname=casename, step=step, rank=rank) + if rst_fname != rst_filename: + rst_data = { + "local_mesh": local_mesh, + "cv": state, + "t": t, + "step": step, + "order": order, + "global_nelements": global_nelements, + "num_parts": num_parts + } + from mirgecom.restart import write_restart_file + write_restart_file(actx, rst_data, rst_fname, comm) + + def my_health_check(pressure): + health_error = False + from mirgecom.simutil import check_naninf_local, check_range_local + if check_naninf_local(dcoll, "vol", pressure) \ + or check_range_local(dcoll, "vol", pressure, .8, 1.6): + health_error = True + logger.info(f"{rank=}: Invalid pressure data found.") + return health_error + + def my_pre_step(step, t, dt, state): + fluid_state = make_fluid_state(state, gas_model) + dv = fluid_state.dv + + try: + + if logmgr: + logmgr.tick_before() + + from mirgecom.simutil import check_step + do_viz = check_step(step=step, interval=nviz) + do_restart = check_step(step=step, interval=nrestart) + do_health = check_step(step=step, interval=nhealth) + + if do_health: + health_errors = global_reduce(my_health_check(dv.pressure), op="lor") + if health_errors: + if rank == 0: + logger.info("Fluid solution failed health check.") + raise MyRuntimeError("Failed simulation health check.") + + if do_restart: + my_write_restart(step=step, t=t, state=state) + + if do_viz: + my_write_viz(step=step, t=t, state=state, dv=dv) + + except MyRuntimeError: + if rank == 0: + logger.info("Errors detected; attempting graceful exit.") + my_write_viz(step=step, t=t, state=state) + my_write_restart(step=step, t=t, state=state) + raise + + dt = get_sim_timestep(dcoll, fluid_state, t, dt, current_cfl, t_final, + constant_cfl) + return state, dt + + def my_post_step(step, t, dt, state): + # Logmgr needs to know about EOS, dt, dim? + # imo this is a design/scope flaw + if logmgr: + set_dt(logmgr, dt) + set_sim_state(logmgr, dim, state, eos) + logmgr.tick_after() + return state, dt + + def my_rhs(t, state): + fluid_state = make_fluid_state(cv=state, gas_model=gas_model) + return euler_operator(dcoll, state=fluid_state, time=t, + boundaries=boundaries, + gas_model=gas_model, use_esdg=use_esdg, + quadrature_tag=quadrature_tag) + + current_dt = get_sim_timestep(dcoll, current_state, current_t, current_dt, + current_cfl, t_final, constant_cfl) + + current_step, current_t, current_cv = \ + advance_state(rhs=my_rhs, timestepper=timestepper, + pre_step_callback=my_pre_step, + post_step_callback=my_post_step, dt=current_dt, + state=current_cv, t=current_t, t_final=t_final) + + # Dump the final data + if rank == 0: + logger.info("Checkpointing final state ...") + final_state = make_fluid_state(current_cv, gas_model) + final_dv = final_state.dv + + my_write_viz(step=current_step, t=current_t, state=current_cv, dv=final_dv) + my_write_restart(step=current_step, t=current_t, state=current_cv) + + if logmgr: + logmgr.close() + elif use_profiling: + print(actx.tabulate_profiling_data()) + + finish_tol = 1e-16 + assert np.abs(current_t - t_final) < finish_tol + + +if __name__ == "__main__": + import argparse + casename = "pulse" + parser = argparse.ArgumentParser(description=f"MIRGE-Com Example: {casename}") + parser.add_argument("--overintegration", action="store_true", + help="use overintegration in the RHS computations") + parser.add_argument("--lazy", action="store_true", + help="switch to a lazy computation mode") + parser.add_argument("--profiling", action="store_true", + help="turn on detailed performance profiling") + parser.add_argument("--leap", action="store_true", + help="use leap timestepper") + parser.add_argument("--esdg", action="store_true", + help="use entropy-stable dg for inviscid terms.") + parser.add_argument("--numpy", action="store_true", + help="use numpy-based eager actx.") + parser.add_argument("--restart_file", help="root name of restart file") + parser.add_argument("--casename", help="casename to use for i/o") + parser.add_argument("--tpe", action="store_true", + help="Use tensor product (quad/hex) elements.") + args = parser.parse_args() + + from warnings import warn + from mirgecom.simutil import ApplicationOptionsError + warn("This version of the pulse example is forced to use quads/hexes.") + + if args.esdg: + if not args.lazy and not args.numpy: + raise ApplicationOptionsError("ESDG requires lazy or numpy context.") + if not args.overintegration: + warn("ESDG requires overintegration, enabling --overintegration.") + + from mirgecom.array_context import get_reasonable_array_context_class + actx_class = get_reasonable_array_context_class( + lazy=args.lazy, distributed=True, profiling=args.profiling, numpy=args.numpy) + + logging.basicConfig(format="%(message)s", level=logging.INFO) + if args.casename: + casename = args.casename + rst_filename = None + if args.restart_file: + rst_filename = args.restart_file + + main(actx_class, use_esdg=args.esdg, + use_overintegration=args.overintegration or args.esdg, + use_leap=args.leap, use_tpe=args.tpe, + casename=casename, rst_filename=rst_filename) + +# vim: foldmethod=marker diff --git a/mirgecom/discretization.py b/mirgecom/discretization.py index 37677b7a3..95dcb715c 100644 --- a/mirgecom/discretization.py +++ b/mirgecom/discretization.py @@ -39,29 +39,42 @@ # examples to use discretization collections, and change it centrally # when we want to change it. def create_discretization_collection(actx, volume_meshes, order, *, - mpi_communicator=None, quadrature_order=-1): + mpi_communicator=None, quadrature_order=-1, + tensor_product_elements=False): """Create and return a grudge DG discretization collection.""" + from warnings import warn if mpi_communicator is not None: - from warnings import warn warn( "mpi_communicator argument is deprecated and will disappear in Q4 2022.", DeprecationWarning, stacklevel=2) + if tensor_product_elements: + warn("Overintegration is not supported for tensor product elements.") + from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from grudge.discretization import make_discretization_collection from meshmode.discretization.poly_element import ( QuadratureSimplexGroupFactory, - PolynomialRecursiveNodesGroupFactory + PolynomialRecursiveNodesGroupFactory, + LegendreGaussLobattoTensorProductGroupFactory as Lgl ) if quadrature_order < 0: quadrature_order = 2*order+1 - return make_discretization_collection( - actx, volume_meshes, - discr_tag_to_group_factory={ - DISCR_TAG_BASE: PolynomialRecursiveNodesGroupFactory(order=order, - family="lgl"), - DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(quadrature_order), - } - ) + if tensor_product_elements: + return make_discretization_collection( + actx, volume_meshes, + discr_tag_to_group_factory={ + DISCR_TAG_BASE: Lgl(order) + } + ) + else: + return make_discretization_collection( + actx, volume_meshes, + discr_tag_to_group_factory={ + DISCR_TAG_BASE: PolynomialRecursiveNodesGroupFactory(order=order, + family="lgl"), + DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(quadrature_order), + } + ) diff --git a/mirgecom/simutil.py b/mirgecom/simutil.py index 1a2cf06d3..76ba6e11c 100644 --- a/mirgecom/simutil.py +++ b/mirgecom/simutil.py @@ -124,7 +124,8 @@ def get_number_of_tetrahedron_nodes(dim, order, include_faces=False): return nnodes -def get_box_mesh(dim, a, b, n, t=None, periodic=None): +def get_box_mesh(dim, a, b, n, t=None, periodic=None, + tensor_product_elements=False, **kwargs): """ Create a rectangular "box" like mesh with tagged boundary faces. @@ -174,10 +175,13 @@ def get_box_mesh(dim, a, b, n, t=None, periodic=None): bttf["-"+str(i+1)] = ["-"+dim_names[i]] bttf["+"+str(i+1)] = ["+"+dim_names[i]] + from meshmode.mesh import TensorProductElementGroup + group_cls = TensorProductElementGroup if tensor_product_elements else None from meshmode.mesh.generation import generate_regular_rect_mesh as gen return gen(a=a, b=b, nelements_per_axis=n, boundary_tag_to_face=bttf, - mesh_type=t, periodic=periodic) + mesh_type=t, periodic=periodic, group_cls=group_cls, + **kwargs) def check_step(step, interval): diff --git a/test/test_operators.py b/test/test_operators.py index 6af6f814b..f08a8fb41 100644 --- a/test/test_operators.py +++ b/test/test_operators.py @@ -133,6 +133,7 @@ def central_flux_boundary(actx, dcoll, soln_func, dd_bdry): return op.project(dcoll, bnd_tpair.dd, dd_allfaces, flux_weak) +@pytest.mark.parametrize("tpe", [False, True]) @pytest.mark.parametrize("dim", [1, 2, 3]) @pytest.mark.parametrize("order", [1, 2, 3]) @pytest.mark.parametrize("sym_test_func_factory", [ @@ -143,7 +144,7 @@ def central_flux_boundary(actx, dcoll, soln_func, dd_bdry): _trig_test_func, _cv_test_func ]) -def test_grad_operator(actx_factory, dim, order, sym_test_func_factory): +def test_grad_operator(actx_factory, tpe, dim, order, sym_test_func_factory): """Test the gradient operator for sanity. Check whether we get the right answers for gradients of analytic functions with @@ -154,8 +155,19 @@ def test_grad_operator(actx_factory, dim, order, sym_test_func_factory): - trig funcs - :class:`~mirgecom.fluid.ConservedVars` composed of funcs from above """ + import pyopencl as cl + from grudge.array_context import PyOpenCLArrayContext + + # This comes from array_context actx = actx_factory() + if tpe: # TPE requires *grudge* array context, not array_context + if dim == 1: # TPE does not support dim=1 + pytest.skip() + ctx = cl.create_some_context() + queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) + sym_test_func = sym_test_func_factory(dim) tol = 1e-10 if dim < 3 else 1e-9 @@ -164,13 +176,14 @@ def test_grad_operator(actx_factory, dim, order, sym_test_func_factory): for nfac in [1, 2, 4]: - mesh = get_box_mesh(dim, a=0, b=1, n=nfac*3) + mesh = get_box_mesh(dim, a=0, b=1, n=nfac*3, tensor_product_elements=tpe) logger.info( f"Number of {dim}d elements: {mesh.nelements}" ) - dcoll = create_discretization_collection(actx, mesh, order=order) + dcoll = create_discretization_collection(actx, mesh, order=order, + tensor_product_elements=tpe) # compute max element size from grudge.dt_utils import h_max_from_volume