def __init__( self, array_context: ArrayContext, mesh: Mesh, order=None, discr_tag_to_group_factory=None, mpi_communicator=None, # FIXME: `quad_tag_to_group_factory` is deprecated quad_tag_to_group_factory=None): """ :arg discr_tag_to_group_factory: A mapping from discretization tags (typically one of: :class:`grudge.dof_desc.DISCR_TAG_BASE`, :class:`grudge.dof_desc.DISCR_TAG_MODAL`, or :class:`grudge.dof_desc.DISCR_TAG_QUAD`) to a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` indicating with which type of discretization the operations are to be carried out, or *None* to indicate that operations with this discretization tag should be carried out with the standard volume discretization. """ if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is not None): raise ValueError( "Both `quad_tag_to_group_factory` and `discr_tag_to_group_factory` " "are specified. Use `discr_tag_to_group_factory` instead.") # FIXME: `quad_tag_to_group_factory` is deprecated if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is None): warn( "`quad_tag_to_group_factory` is a deprecated kwarg and will " "be dropped in version 2022.x. Use `discr_tag_to_group_factory` " "instead.", DeprecationWarning, stacklevel=2) discr_tag_to_group_factory = quad_tag_to_group_factory self._setup_actx = array_context.clone() from meshmode.discretization.poly_element import \ default_simplex_group_factory if discr_tag_to_group_factory is None: if order is None: raise TypeError( "one of 'order' and 'discr_tag_to_group_factory' must be given" ) discr_tag_to_group_factory = { DISCR_TAG_BASE: default_simplex_group_factory(base_dim=mesh.dim, order=order) } else: if order is not None: discr_tag_to_group_factory = discr_tag_to_group_factory.copy() if DISCR_TAG_BASE in discr_tag_to_group_factory: raise ValueError( "if 'order' is given, 'discr_tag_to_group_factory' must " "not have a key of DISCR_TAG_BASE") discr_tag_to_group_factory[DISCR_TAG_BASE] = \ default_simplex_group_factory(base_dim=mesh.dim, order=order) # Modal discr should always come from the base discretization discr_tag_to_group_factory[DISCR_TAG_MODAL] = \ _generate_modal_group_factory( discr_tag_to_group_factory[DISCR_TAG_BASE] ) self.discr_tag_to_group_factory = discr_tag_to_group_factory from meshmode.discretization import Discretization self._volume_discr = Discretization( array_context, mesh, self.group_factory_for_discretization_tag(DISCR_TAG_BASE)) # NOTE: Can be removed when symbolics are completely removed # {{{ management of discretization-scoped common subexpressions from pytools import UniqueNameGenerator self._discr_scoped_name_gen = UniqueNameGenerator() self._discr_scoped_subexpr_to_name = {} self._discr_scoped_subexpr_name_to_value = {} # }}} self._dist_boundary_connections = \ self._set_up_distributed_communication( mpi_communicator, array_context) self.mpi_communicator = mpi_communicator
def main(ctx_factory=cl.create_some_context, use_logmgr=True, use_overintegration=False, use_leap=False, use_profiling=False, casename=None, rst_filename=None, actx_class=PyOpenCLArrayContext): """Drive the example.""" cl_ctx = ctx_factory() 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(use_logmgr, filename=f"{casename}.sqlite", mode="wu", mpi_comm=comm) if use_profiling: queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) else: queue = cl.CommandQueue(cl_ctx) actx = actx_class( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) # timestepping control current_step = 0 if use_leap: from leap.rk import RK4MethodBuilder timestepper = RK4MethodBuilder("state") else: timestepper = rk4_step t_final = 0.1 current_cfl = 1.0 current_dt = .01 current_t = 0 constant_cfl = False # some i/o frequencies nstatus = 1 nrestart = 5 nviz = 10 nhealth = 1 dim = 3 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 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) local_mesh, global_nelements = generate_and_distribute_mesh(comm, generate_mesh) local_nelements = local_mesh.nelements from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory order = 1 discr = EagerDGDiscretization( actx, local_mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=local_mesh.dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) }, mpi_communicator=comm ) nodes = thaw(discr.nodes(), actx) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = 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, discr, 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() gas_model = GasModel(eos=eos) vel = np.zeros(shape=(dim,)) orig = np.zeros(shape=(dim,)) initializer = Lump(dim=dim, center=orig, velocity=vel, rhoamp=0.0) wall = AdiabaticSlipBoundary() boundaries = {BTAG_ALL: wall} uniform_state = initializer(nodes) acoustic_pulse = AcousticPulse(dim=dim, amplitude=1.0, width=.1, center=orig) if rst_filename: current_t = restart_data["t"] current_step = restart_data["step"] current_cv = restart_data["cv"] if logmgr: from mirgecom.logging_quantities import logmgr_set_time logmgr_set_time(logmgr, current_step, current_t) 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) visualizer = make_visualizer(discr) 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(discr, viz_fields, visualizer, vizname=casename, step=step, t=t, overwrite=True, vis_timer=vis_timer) 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(discr, "vol", pressure) \ or check_range_local(discr, "vol", pressure, .8, 1.5): 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(discr, 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(discr, state=fluid_state, time=t, boundaries=boundaries, gas_model=gas_model, quadrature_tag=quadrature_tag) current_dt = get_sim_timestep(discr, 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
def main(ctx_factory, dim=2, order=3, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(-0.5,)*dim, b=(0.5,)*dim, nelements_per_axis=(nel_1d,)*dim) logger.info("%d elements", mesh.nelements) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory, \ default_simplex_group_factory dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory(base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(3*order), } ) # bounded above by 1 c = 0.2 + 0.8*bump(actx, dcoll, center=np.zeros(3), width=0.5) dt = 0.5 * estimate_rk4_timestep(actx, dcoll, c=1) fields = flat_obj_array( bump(actx, dcoll, ), [dcoll.zeros(actx) for i in range(dcoll.dim)] ) vis = make_visualizer(dcoll) def rhs(t, w): return wave_operator(dcoll, c=c, w=w) logger.info("dt = %g", dt) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: logger.info(f"step: {istep} t: {t} " f"L2: {op.norm(dcoll, fields[0], 2)} " f"Linf: {op.norm(dcoll, fields[0], np.inf)} " f"sol max: {op.nodal_max(dcoll, 'vol', fields[0])} " f"sol min: {op.nodal_min(dcoll, 'vol', fields[0])}") if visualize: vis.write_vtk_file( f"fld-wave-eager-var-velocity-{istep:04d}.vtu", [ ("c", c), ("u", fields[0]), ("v", fields[1:]), ] ) t += dt istep += 1 # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert op.norm(dcoll, fields[0], 2) < 1
def main(ctx_factory=cl.create_some_context, use_logmgr=True, use_leap=False, use_overintegration=False, use_profiling=False, casename=None, rst_filename=None, actx_class=PyOpenCLArrayContext, log_dependent=True): """Drive example.""" cl_ctx = ctx_factory() if casename is None: casename = "mirgecom" from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() nproc = comm.Get_size() from mirgecom.simutil import global_reduce as _global_reduce global_reduce = partial(_global_reduce, comm=comm) logmgr = initialize_logmgr(use_logmgr, filename=f"{casename}.sqlite", mode="wu", mpi_comm=comm) if use_profiling: queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) else: queue = cl.CommandQueue(cl_ctx) actx = actx_class(queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) # Some discretization parameters dim = 2 nel_1d = 8 order = 1 # {{{ Time stepping control # This example runs only 3 steps by default (to keep CI ~short) # With the mixture defined below, equilibrium is achieved at ~40ms # To run to equilibrium, set t_final >= 40ms. # Time stepper selection if use_leap: from leap.rk import RK4MethodBuilder timestepper = RK4MethodBuilder("state") else: timestepper = rk4_step # Time loop control parameters current_step = 0 t_final = 1e-8 current_cfl = 1.0 current_dt = 1e-9 current_t = 0 constant_cfl = False # i.o frequencies nstatus = 1 nviz = 5 nhealth = 1 nrestart = 5 # }}} Time stepping control debug = False 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"] == nproc rst_time = restart_data["t"] rst_step = restart_data["step"] rst_order = restart_data["order"] else: # generate the grid from scratch from meshmode.mesh.generation import generate_regular_rect_mesh box_ll = -0.005 box_ur = 0.005 generate_mesh = partial(generate_regular_rect_mesh, a=(box_ll, ) * dim, b=(box_ur, ) * dim, nelements_per_axis=(nel_1d, ) * dim) local_mesh, global_nelements = generate_and_distribute_mesh( comm, generate_mesh) local_nelements = local_mesh.nelements from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, local_mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory(base_dim=local_mesh.dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2 * order + 1) }, mpi_communicator=comm) nodes = thaw(discr.nodes(), actx) ones = discr.zeros(actx) + 1.0 if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None ones = discr.zeros(actx) + 1.0 vis_timer = None if logmgr: logmgr_add_cl_device_info(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) 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"), ("t_step.max", "------- step walltime: {value:6g} s, "), ("t_log.max", "log walltime: {value:6g} s")]) if log_dependent: logmgr_add_many_discretization_quantities( logmgr, discr, dim, extract_vars_for_logging, units_for_logging) logmgr.add_watches([ ("min_pressure", "\n------- P (min, max) (Pa) = ({value:1.9e}, "), ("max_pressure", "{value:1.9e})\n"), ("min_temperature", "------- T (min, max) (K) = ({value:7g}, "), ("max_temperature", "{value:7g})\n") ]) # {{{ Set up initial state using Cantera # Use Cantera for initialization # -- Pick up a CTI for the thermochemistry config # --- Note: Users may add their own CTI file by dropping it into # --- mirgecom/mechanisms alongside the other CTI files. from mirgecom.mechanisms import get_mechanism_cti mech_cti = get_mechanism_cti("uiuc") cantera_soln = cantera.Solution(phase_id="gas", source=mech_cti) nspecies = cantera_soln.n_species # Initial temperature, pressure, and mixutre mole fractions are needed to # set up the initial state in Cantera. temperature_seed = 1500.0 # Initial temperature hot enough to burn # Parameters for calculating the amounts of fuel, oxidizer, and inert species equiv_ratio = 1.0 ox_di_ratio = 0.21 stoich_ratio = 3.0 # Grab the array indices for the specific species, ethylene, oxygen, and nitrogen i_fu = cantera_soln.species_index("C2H4") i_ox = cantera_soln.species_index("O2") i_di = cantera_soln.species_index("N2") x = np.zeros(nspecies) # Set the species mole fractions according to our desired fuel/air mixture x[i_fu] = (ox_di_ratio * equiv_ratio) / (stoich_ratio + ox_di_ratio * equiv_ratio) x[i_ox] = stoich_ratio * x[i_fu] / equiv_ratio x[i_di] = (1.0 - ox_di_ratio) * x[i_ox] / ox_di_ratio # Uncomment next line to make pylint fail when it can't find cantera.one_atm one_atm = cantera.one_atm # pylint: disable=no-member # one_atm = 101325.0 # Let the user know about how Cantera is being initilized print(f"Input state (T,P,X) = ({temperature_seed}, {one_atm}, {x}") # Set Cantera internal gas temperature, pressure, and mole fractios cantera_soln.TPX = temperature_seed, one_atm, x # Pull temperature, total density, mass fractions, and pressure from Cantera # We need total density, and mass fractions to initialize the fluid/gas state. can_t, can_rho, can_y = cantera_soln.TDY can_p = cantera_soln.P # *can_t*, *can_p* should not differ (significantly) from user's initial data, # but we want to ensure that we use exactly the same starting point as Cantera, # so we use Cantera's version of these data. # }}} # {{{ Create Pyrometheus thermochemistry object & EOS # Create a Pyrometheus EOS with the Cantera soln. Pyrometheus uses Cantera and # generates a set of methods to calculate chemothermomechanical properties and # states for this particular mechanism. from mirgecom.thermochemistry import make_pyrometheus_mechanism_class pyro_mechanism = make_pyrometheus_mechanism_class(cantera_soln)(actx.np) eos = PyrometheusMixture(pyro_mechanism, temperature_guess=temperature_seed) gas_model = GasModel(eos=eos) from pytools.obj_array import make_obj_array def get_temperature_update(cv, temperature): y = cv.species_mass_fractions e = gas_model.eos.internal_energy(cv) / cv.mass return pyro_mechanism.get_temperature_update_energy(e, temperature, y) from mirgecom.gas_model import make_fluid_state def get_fluid_state(cv, tseed): return make_fluid_state(cv=cv, gas_model=gas_model, temperature_seed=tseed) compute_temperature_update = actx.compile(get_temperature_update) construct_fluid_state = actx.compile(get_fluid_state) # }}} # {{{ MIRGE-Com state initialization # Initialize the fluid/gas state with Cantera-consistent data: # (density, pressure, temperature, mass_fractions) print(f"Cantera state (rho,T,P,Y) = ({can_rho}, {can_t}, {can_p}, {can_y}") velocity = np.zeros(shape=(dim, )) initializer = MixtureInitializer(dim=dim, nspecies=nspecies, pressure=can_p, temperature=can_t, massfractions=can_y, velocity=velocity) my_boundary = AdiabaticSlipBoundary() boundaries = {BTAG_ALL: my_boundary} if rst_filename: current_step = rst_step current_t = rst_time if logmgr: from mirgecom.logging_quantities import logmgr_set_time logmgr_set_time(logmgr, current_step, current_t) if order == rst_order: current_cv = restart_data["cv"] temperature_seed = restart_data["temperature_seed"] else: rst_cv = restart_data["cv"] old_discr = EagerDGDiscretization(actx, local_mesh, order=rst_order, mpi_communicator=comm) from meshmode.discretization.connection import make_same_mesh_connection connection = make_same_mesh_connection( actx, discr.discr_from_dd("vol"), old_discr.discr_from_dd("vol")) current_cv = connection(rst_cv) temperature_seed = connection(restart_data["temperature_seed"]) else: # Set the current state from time 0 current_cv = initializer(eos=gas_model.eos, x_vec=nodes) temperature_seed = temperature_seed * ones # The temperature_seed going into this function is: # - At time 0: the initial temperature input data (maybe from Cantera) # - On restart: the restarted temperature seed from restart file (saving # the *seed* allows restarts to be deterministic current_fluid_state = construct_fluid_state(current_cv, temperature_seed) current_dv = current_fluid_state.dv temperature_seed = current_dv.temperature # Inspection at physics debugging time if debug: print("Initial MIRGE-Com state:") print(f"Initial DV pressure: {current_fluid_state.pressure}") print(f"Initial DV temperature: {current_fluid_state.temperature}") # }}} visualizer = make_visualizer(discr) initname = initializer.__class__.__name__ eosname = gas_model.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) # Cantera equilibrate calculates the expected end state @ chemical equilibrium # i.e. the expected state after all reactions cantera_soln.equilibrate("UV") eq_temperature, eq_density, eq_mass_fractions = cantera_soln.TDY eq_pressure = cantera_soln.P # Report the expected final state to the user if rank == 0: logger.info(init_message) logger.info(f"Expected equilibrium state:" f" {eq_pressure=}, {eq_temperature=}," f" {eq_density=}, {eq_mass_fractions=}") def my_write_status(dt, cfl, dv=None): status_msg = f"------ {dt=}" if constant_cfl else f"----- {cfl=}" if ((dv is not None) and (not log_dependent)): temp = dv.temperature press = dv.pressure from grudge.op import nodal_min_loc, nodal_max_loc tmin = allsync(actx.to_numpy(nodal_min_loc(discr, "vol", temp)), comm=comm, op=MPI.MIN) tmax = allsync(actx.to_numpy(nodal_max_loc(discr, "vol", temp)), comm=comm, op=MPI.MAX) pmin = allsync(actx.to_numpy(nodal_min_loc(discr, "vol", press)), comm=comm, op=MPI.MIN) pmax = allsync(actx.to_numpy(nodal_max_loc(discr, "vol", press)), comm=comm, op=MPI.MAX) dv_status_msg = f"\nP({pmin}, {pmax}), T({tmin}, {tmax})" status_msg = status_msg + dv_status_msg if rank == 0: logger.info(status_msg) def my_write_viz(step, t, dt, state, ts_field, dv, production_rates, cfl): viz_fields = [("cv", state), ("dv", dv), ("production_rates", production_rates), ("dt" if constant_cfl else "cfl", ts_field)] write_visfile(discr, viz_fields, visualizer, vizname=casename, step=step, t=t, overwrite=True, vis_timer=vis_timer) def my_write_restart(step, t, state, temperature_seed): rst_fname = rst_pattern.format(cname=casename, step=step, rank=rank) if rst_fname == rst_filename: if rank == 0: logger.info("Skipping overwrite of restart file.") else: rst_data = { "local_mesh": local_mesh, "cv": state.cv, "temperature_seed": temperature_seed, "t": t, "step": step, "order": order, "global_nelements": global_nelements, "num_parts": nproc } from mirgecom.restart import write_restart_file write_restart_file(actx, rst_data, rst_fname, comm) def my_health_check(cv, dv): import grudge.op as op health_error = False pressure = dv.pressure temperature = dv.temperature from mirgecom.simutil import check_naninf_local, check_range_local if check_naninf_local(discr, "vol", pressure): health_error = True logger.info(f"{rank=}: Invalid pressure data found.") if check_range_local(discr, "vol", pressure, 1e5, 2.6e5): health_error = True logger.info(f"{rank=}: Pressure range violation.") if check_naninf_local(discr, "vol", temperature): health_error = True logger.info(f"{rank=}: Invalid temperature data found.") if check_range_local(discr, "vol", temperature, 1.498e3, 1.6e3): health_error = True logger.info(f"{rank=}: Temperature range violation.") # This check is the temperature convergence check # The current *temperature* is what Pyrometheus gets # after a fixed number of Newton iterations, *n_iter*. # Calling `compute_temperature` here with *temperature* # input as the guess returns the calculated gas temperature after # yet another *n_iter*. # The difference between those two temperatures is the # temperature residual, which can be used as an indicator of # convergence in Pyrometheus `get_temperature`. # Note: The local max jig below works around a very long compile # in lazy mode. temp_resid = compute_temperature_update(cv, temperature) / temperature temp_err = (actx.to_numpy(op.nodal_max_loc(discr, "vol", temp_resid))) if temp_err > 1e-8: health_error = True logger.info( f"{rank=}: Temperature is not converged {temp_resid=}.") return health_error from mirgecom.inviscid import get_inviscid_timestep def get_dt(state): return get_inviscid_timestep(discr, state=state) compute_dt = actx.compile(get_dt) from mirgecom.inviscid import get_inviscid_cfl def get_cfl(state, dt): return get_inviscid_cfl(discr, dt=dt, state=state) compute_cfl = actx.compile(get_cfl) def get_production_rates(cv, temperature): return eos.get_production_rates(cv, temperature) compute_production_rates = actx.compile(get_production_rates) def my_get_timestep(t, dt, state): # richer interface to calculate {dt,cfl} returns node-local estimates t_remaining = max(0, t_final - t) if constant_cfl: ts_field = current_cfl * compute_dt(state) from grudge.op import nodal_min_loc dt = allsync(actx.to_numpy(nodal_min_loc(discr, "vol", ts_field)), comm=comm, op=MPI.MIN) cfl = current_cfl else: ts_field = compute_cfl(state, current_dt) from grudge.op import nodal_max_loc cfl = allsync(actx.to_numpy(nodal_max_loc(discr, "vol", ts_field)), comm=comm, op=MPI.MAX) return ts_field, cfl, min(t_remaining, dt) def my_pre_step(step, t, dt, state): cv, tseed = state fluid_state = construct_fluid_state(cv, tseed) 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) do_status = check_step(step=step, interval=nstatus) if do_health: health_errors = global_reduce(my_health_check(cv, dv), op="lor") if health_errors: if rank == 0: logger.info("Fluid solution failed health check.") raise MyRuntimeError("Failed simulation health check.") ts_field, cfl, dt = my_get_timestep(t=t, dt=dt, state=fluid_state) if do_status: my_write_status(dt=dt, cfl=cfl, dv=dv) if do_restart: my_write_restart(step=step, t=t, state=fluid_state, temperature_seed=tseed) if do_viz: production_rates = compute_production_rates( fluid_state.cv, fluid_state.temperature) my_write_viz(step=step, t=t, dt=dt, state=cv, dv=dv, production_rates=production_rates, ts_field=ts_field, cfl=cfl) except MyRuntimeError: if rank == 0: logger.info("Errors detected; attempting graceful exit.") # my_write_viz(step=step, t=t, dt=dt, state=cv) # my_write_restart(step=step, t=t, state=fluid_state) raise return state, dt def my_post_step(step, t, dt, state): cv, tseed = state fluid_state = construct_fluid_state(cv, tseed) # 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, cv, gas_model.eos) logmgr.tick_after() return make_obj_array([cv, fluid_state.temperature]), dt def my_rhs(t, state): cv, tseed = state from mirgecom.gas_model import make_fluid_state fluid_state = make_fluid_state(cv=cv, gas_model=gas_model, temperature_seed=tseed) return make_obj_array([ euler_operator(discr, state=fluid_state, time=t, boundaries=boundaries, gas_model=gas_model, quadrature_tag=quadrature_tag) + eos.get_species_source_terms(cv, fluid_state.temperature), 0 * tseed ]) current_dt = get_sim_timestep(discr, current_fluid_state, current_t, current_dt, current_cfl, t_final, constant_cfl) current_step, current_t, current_state = \ advance_state(rhs=my_rhs, timestepper=timestepper, pre_step_callback=my_pre_step, post_step_callback=my_post_step, dt=current_dt, state=make_obj_array([current_cv, temperature_seed]), t=current_t, t_final=t_final) # Dump the final data if rank == 0: logger.info("Checkpointing final state ...") final_cv, tseed = current_state final_fluid_state = construct_fluid_state(final_cv, tseed) final_dv = final_fluid_state.dv final_dm = compute_production_rates(final_cv, final_dv.temperature) ts_field, cfl, dt = my_get_timestep(t=current_t, dt=current_dt, state=final_fluid_state) my_write_viz(step=current_step, t=current_t, dt=dt, state=final_cv, dv=final_dv, production_rates=final_dm, ts_field=ts_field, cfl=cfl) my_write_status(dt=dt, cfl=cfl, dv=final_dv) my_write_restart(step=current_step, t=current_t, state=final_fluid_state, temperature_seed=tseed) 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
def test_uniform_rhs(actx_factory, nspecies, dim, order, use_overintegration): """Test the inviscid rhs using a trivial constant/uniform state. This state should yield rhs = 0 to FP. The test is performed for 1, 2, and 3 dimensions, with orders 1, 2, and 3, with and without passive species. """ actx = actx_factory() tolerance = 1e-9 from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() # for nel_1d in [4, 8, 12]: for nel_1d in [4, 8]: from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(-0.5,) * dim, b=(0.5,) * dim, nelements_per_axis=(nel_1d,) * dim ) logger.info( f"Number of {dim}d elements: {mesh.nelements}" ) from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1 energy_input = discr.zeros(actx) + 2.5 mom_input = make_obj_array( [discr.zeros(actx) for i in range(discr.dim)] ) mass_frac_input = flat_obj_array( [ones / ((i + 1) * 10) for i in range(nspecies)] ) species_mass_input = mass_input * mass_frac_input num_equations = dim + 2 + len(species_mass_input) cv = make_conserved( dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(cv, gas_model) expected_rhs = make_conserved( dim, q=make_obj_array([discr.zeros(actx) for i in range(num_equations)]) ) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass rho_rhs = inviscid_rhs.mass rhoe_rhs = inviscid_rhs.energy rhov_rhs = inviscid_rhs.momentum rhoy_rhs = inviscid_rhs.species_mass logger.info( f"rho_rhs = {rho_rhs}\n" f"rhoe_rhs = {rhoe_rhs}\n" f"rhov_rhs = {rhov_rhs}\n" f"rhoy_rhs = {rhoy_rhs}\n" ) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) assert inf_norm(rho_resid) < tolerance assert inf_norm(rhoe_resid) < tolerance for i in range(dim): assert inf_norm(mom_resid[i]) < tolerance for i in range(nspecies): assert inf_norm(rhoy_resid[i]) < tolerance err_max = inf_norm(rho_resid) eoc_rec0.add_data_point(1.0 / nel_1d, err_max) # set a non-zero, but uniform velocity component for i in range(len(mom_input)): mom_input[i] = discr.zeros(actx) + (-1.0) ** i cv = make_conserved( dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(cv, gas_model) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0) rhs_resid = inviscid_rhs - expected_rhs rho_resid = rhs_resid.mass rhoe_resid = rhs_resid.energy mom_resid = rhs_resid.momentum rhoy_resid = rhs_resid.species_mass assert inf_norm(rho_resid) < tolerance assert inf_norm(rhoe_resid) < tolerance for i in range(dim): assert inf_norm(mom_resid[i]) < tolerance for i in range(nspecies): assert inf_norm(rhoy_resid[i]) < tolerance err_max = inf_norm(rho_resid) eoc_rec1.add_data_point(1.0 / nel_1d, err_max) logger.info( f"V == 0 Errors:\n{eoc_rec0}" f"V != 0 Errors:\n{eoc_rec1}" ) assert ( eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9 ) assert ( eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9 )
def _euler_flow_stepper(actx, parameters): logging.basicConfig(format="%(message)s", level=logging.INFO) mesh = parameters["mesh"] t = parameters["time"] order = parameters["order"] t_final = parameters["tfinal"] initializer = parameters["initializer"] exittol = parameters["exittol"] casename = parameters["casename"] boundaries = parameters["boundaries"] eos = parameters["eos"] cfl = parameters["cfl"] dt = parameters["dt"] constantcfl = parameters["constantcfl"] nstepstatus = parameters["nstatus"] use_overintegration = parameters["use_overintegration"] if t_final <= t: return(0.0) rank = 0 dim = mesh.dim istep = 0 from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None nodes = thaw(discr.nodes(), actx) cv = initializer(nodes) gas_model = GasModel(eos=eos) fluid_state = make_fluid_state(cv, gas_model) sdt = cfl * get_inviscid_timestep(discr, fluid_state) initname = initializer.__class__.__name__ eosname = eos.__class__.__name__ logger.info( f"Num {dim}d order-{order} elements: {mesh.nelements}\n" f"Timestep: {dt}\n" f"Final time: {t_final}\n" f"Status freq: {nstepstatus}\n" f"Initialization: {initname}\n" f"EOS: {eosname}" ) vis = make_visualizer(discr, order) def write_soln(state, write_status=True): dv = eos.dependent_vars(cv=state) expected_result = initializer(nodes, t=t) result_resid = state - expected_result maxerr = [np.max(np.abs(result_resid[i].get())) for i in range(dim + 2)] mindv = [np.min(dvfld.get()) for dvfld in dv] maxdv = [np.max(dvfld.get()) for dvfld in dv] if write_status is True: statusmsg = ( f"Status: Step({istep}) Time({t})\n" f"------ P({mindv[0]},{maxdv[0]})\n" f"------ T({mindv[1]},{maxdv[1]})\n" f"------ dt,cfl = ({dt},{cfl})\n" f"------ Err({maxerr})" ) logger.info(statusmsg) io_fields = ["cv", state] io_fields += eos.split_fields(dim, dv) io_fields.append(("exact_soln", expected_result)) io_fields.append(("residual", result_resid)) nameform = casename + "-{iorank:04d}-{iostep:06d}.vtu" visfilename = nameform.format(iorank=rank, iostep=istep) vis.write_vtk_file(visfilename, io_fields) return maxerr def rhs(t, q): fluid_state = make_fluid_state(q, gas_model) return euler_operator(discr, fluid_state, boundaries=boundaries, gas_model=gas_model, time=t, quadrature_tag=quadrature_tag) filter_order = 8 eta = .5 alpha = -1.0*np.log(np.finfo(float).eps) nummodes = int(1) for _ in range(dim): nummodes *= int(order + dim + 1) nummodes /= math.factorial(int(dim)) cutoff = int(eta * order) from mirgecom.filter import ( exponential_mode_response_function as xmrfunc, filter_modally ) frfunc = partial(xmrfunc, alpha=alpha, filter_order=filter_order) while t < t_final: if constantcfl is True: dt = sdt else: cfl = dt / sdt if nstepstatus > 0: if istep % nstepstatus == 0: write_soln(state=cv) cv = rk4_step(cv, t, dt, rhs) cv = filter_modally(discr, "vol", cutoff, frfunc, cv) t += dt istep += 1 sdt = cfl * get_inviscid_timestep(discr, fluid_state) if nstepstatus > 0: logger.info("Writing final dump.") maxerr = max(write_soln(cv, False)) else: expected_result = initializer(nodes, time=t) maxerr = max_component_norm(discr, cv-expected_result, np.inf) logger.info(f"Max Error: {maxerr}") if maxerr > exittol: raise ValueError("Solution failed to follow expected result.") return(maxerr)
def test_multilump_rhs(actx_factory, dim, order, v0, use_overintegration): """Test the Euler rhs using the non-trivial 1, 2, and 3D mass lump case. The case is tested against the analytic expressions of the RHS. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() nspecies = 10 tolerance = 1e-8 maxxerr = 0.0 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for nel_1d in [4, 8, 12]: from meshmode.mesh.generation import ( generate_regular_rect_mesh, ) mesh = generate_regular_rect_mesh( a=(-1,) * dim, b=(1,) * dim, nelements_per_axis=(nel_1d,) * dim, ) logger.info(f"Number of elements: {mesh.nelements}") from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None nodes = thaw(discr.nodes(), actx) centers = make_obj_array([np.zeros(shape=(dim,)) for i in range(nspecies)]) spec_y0s = np.ones(shape=(nspecies,)) spec_amplitudes = np.ones(shape=(nspecies,)) velocity = np.zeros(shape=(dim,)) velocity[0] = v0 rho0 = 2.0 lump = MulticomponentLump(dim=dim, nspecies=nspecies, rho0=rho0, spec_centers=centers, velocity=velocity, spec_y0s=spec_y0s, spec_amplitudes=spec_amplitudes) lump_soln = lump(nodes) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(lump_soln, gas_model) def _my_boundary(discr, btag, gas_model, state_minus, **kwargs): actx = state_minus.array_context bnd_discr = discr.discr_from_dd(btag) nodes = thaw(bnd_discr.nodes(), actx) return make_fluid_state(lump(x_vec=nodes, **kwargs), gas_model) boundaries = { BTAG_ALL: PrescribedFluidBoundary(boundary_state_func=_my_boundary) } inviscid_rhs = euler_operator( discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag ) expected_rhs = lump.exact_rhs(discr, cv=lump_soln, time=0) print(f"inviscid_rhs = {inviscid_rhs}") print(f"expected_rhs = {expected_rhs}") err_max = actx.to_numpy( discr.norm((inviscid_rhs-expected_rhs), np.inf)) if err_max > maxxerr: maxxerr = err_max eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info(f"Max error: {maxxerr}") logger.info( f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}" ) assert ( eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < tolerance )
def test_vortex_rhs(actx_factory, order, use_overintegration): """Test the inviscid rhs using the non-trivial 2D isentropic vortex. The case is configured to yield rhs = 0. Checks several different orders and refinement levels to check error behavior. """ actx = actx_factory() dim = 2 from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() from meshmode.mesh.generation import generate_regular_rect_mesh for nel_1d in [32, 48, 64]: mesh = generate_regular_rect_mesh( a=(-5,) * dim, b=(5,) * dim, nelements_per_axis=(nel_1d,) * dim, ) logger.info( f"Number of {dim}d elements: {mesh.nelements}" ) from grudge.dof_desc import DISCR_TAG_BASE, DISCR_TAG_QUAD from meshmode.discretization.poly_element import \ default_simplex_group_factory, QuadratureSimplexGroupFactory discr = EagerDGDiscretization( actx, mesh, discr_tag_to_group_factory={ DISCR_TAG_BASE: default_simplex_group_factory( base_dim=dim, order=order), DISCR_TAG_QUAD: QuadratureSimplexGroupFactory(2*order + 1) } ) if use_overintegration: quadrature_tag = DISCR_TAG_QUAD else: quadrature_tag = None nodes = thaw(discr.nodes(), actx) # Init soln with Vortex and expected RHS = 0 vortex = Vortex2D(center=[0, 0], velocity=[0, 0]) vortex_soln = vortex(nodes) gas_model = GasModel(eos=IdealSingleGas()) fluid_state = make_fluid_state(vortex_soln, gas_model) def _vortex_boundary(discr, btag, gas_model, state_minus, **kwargs): actx = state_minus.array_context bnd_discr = discr.discr_from_dd(btag) nodes = thaw(bnd_discr.nodes(), actx) return make_fluid_state(vortex(x_vec=nodes, **kwargs), gas_model) boundaries = { BTAG_ALL: PrescribedFluidBoundary(boundary_state_func=_vortex_boundary) } inviscid_rhs = euler_operator( discr, state=fluid_state, gas_model=gas_model, boundaries=boundaries, time=0.0, quadrature_tag=quadrature_tag) err_max = max_component_norm(discr, inviscid_rhs, np.inf) eoc_rec.add_data_point(1.0 / nel_1d, err_max) logger.info( f"Error for (dim,order) = ({dim},{order}):\n" f"{eoc_rec}" ) assert ( eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11 )
def main(ctx_factory, dim=2, order=4, use_quad=False, visualize=False): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), force_device_scalars=True, ) # {{{ parameters # sphere radius radius = 1.0 # sphere resolution resolution = 64 if dim == 2 else 1 # final time final_time = np.pi # flux flux_type = "lf" # }}} # {{{ discretization if dim == 2: from meshmode.mesh.generation import make_curve_mesh, ellipse mesh = make_curve_mesh( lambda t: radius * ellipse(1.0, t), np.linspace(0.0, 1.0, resolution + 1), order) elif dim == 3: from meshmode.mesh.generation import generate_icosphere mesh = generate_icosphere(radius, order=4 * order, uniform_refinement_rounds=resolution) else: raise ValueError("unsupported dimension") discr_tag_to_group_factory = {} if use_quad: qtag = dof_desc.DISCR_TAG_QUAD else: qtag = None from meshmode.discretization.poly_element import \ default_simplex_group_factory, \ QuadratureSimplexGroupFactory discr_tag_to_group_factory[dof_desc.DISCR_TAG_BASE] = \ default_simplex_group_factory(base_dim=dim-1, order=order) if use_quad: discr_tag_to_group_factory[qtag] = \ QuadratureSimplexGroupFactory(order=4*order) from grudge import DiscretizationCollection dcoll = DiscretizationCollection( actx, mesh, discr_tag_to_group_factory=discr_tag_to_group_factory ) volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume_discr.ndofs) logger.info("nelements: %d", volume_discr.mesh.nelements) # }}} # {{{ Surface advection operator # velocity field x = thaw(dcoll.nodes(), actx) c = make_obj_array([-x[1], x[0], 0.0])[:dim] def f_initial_condition(x): return x[0] from grudge.models.advection import SurfaceAdvectionOperator adv_operator = SurfaceAdvectionOperator( dcoll, c, flux_type=flux_type, quad_tag=qtag ) u0 = f_initial_condition(x) def rhs(t, u): return adv_operator.operator(t, u) # check velocity is tangential from grudge.geometry import normal surf_normal = normal(actx, dcoll, dd=dof_desc.DD_VOLUME) error = op.norm(dcoll, c.dot(surf_normal), 2) logger.info("u_dot_n: %.5e", error) # }}} # {{{ time stepping # FIXME: dt estimate is not necessarily valid for surfaces dt = actx.to_numpy( 0.45 * adv_operator.estimate_rk4_timestep(actx, dcoll, fields=u0)) nsteps = int(final_time // dt) + 1 logger.info("dt: %.5e", dt) logger.info("nsteps: %d", nsteps) from grudge.shortcuts import set_up_rk4 dt_stepper = set_up_rk4("u", dt, u0, rhs) plot = Plotter(actx, dcoll, order, visualize=visualize) norm_u = actx.to_numpy(op.norm(dcoll, u0, 2)) step = 0 event = dt_stepper.StateComputed(0.0, 0, 0, u0) plot(event, "fld-surface-%04d" % 0) if visualize and dim == 3: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll) vis.write_vtk_file( "fld-surface-velocity.vtu", [ ("u", c), ("n", surf_normal) ], overwrite=True ) df = dof_desc.DOFDesc(FACE_RESTR_INTERIOR) face_discr = dcoll.discr_from_dd(df) face_normal = thaw(dcoll.normal(dd=df), actx) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, face_discr) vis.write_vtk_file("fld-surface-face-normals.vtu", [ ("n", face_normal) ], overwrite=True) for event in dt_stepper.run(t_end=final_time, max_steps=nsteps + 1): if not isinstance(event, dt_stepper.StateComputed): continue step += 1 if step % 10 == 0: norm_u = actx.to_numpy(op.norm(dcoll, event.state_component, 2)) plot(event, "fld-surface-%04d" % step) logger.info("[%04d] t = %.5f |u| = %.5e", step, event.t, norm_u) # NOTE: These are here to ensure the solution is bounded for the # time interval specified assert norm_u < 3