def test_inviscid_flux_components(actx_factory, dim): """Test uniform pressure case. Checks that the Euler-internal inviscid flux routine :func:`mirgecom.inviscid.inviscid_flux` returns exactly the expected result with a constant pressure and no flow. Expected inviscid flux is: F(q) = <rhoV, (E+p)V, rho(V.x.V) + pI> Checks that only diagonal terms of the momentum flux: [ rho(V.x.V) + pI ] are non-zero and return the correctly calculated p. """ actx = actx_factory() eos = IdealSingleGas() p0 = 1.0 nel_1d = 4 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) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) eos = IdealSingleGas() logger.info(f"Number of {dim}d elems: {mesh.nelements}") # === this next block tests 1,2,3 dimensions, # with single and multiple nodes/states. The # purpose of this block is to ensure that when # all components of V = 0, the flux recovers # the expected values (and p0 within tolerance) # === with V = 0, fixed P = p0 tolerance = 1e-15 nodes = thaw(actx, discr.nodes()) mass = discr.zeros(actx) + np.dot(nodes, nodes) + 1.0 mom = make_obj_array([discr.zeros(actx) for _ in range(dim)]) p_exact = discr.zeros(actx) + p0 energy = p_exact / 0.4 + 0.5 * np.dot(mom, mom) / mass cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) p = eos.pressure(cv) flux = inviscid_flux(discr, eos, cv) assert discr.norm(p - p_exact, np.inf) < tolerance logger.info(f"{dim}d flux = {flux}") # for velocity zero, these components should be == zero assert discr.norm(flux.mass, 2) == 0.0 assert discr.norm(flux.energy, 2) == 0.0 # The momentum diagonal should be p # Off-diagonal should be identically 0 assert discr.norm(flux.momentum - p0 * np.identity(dim), np.inf) < tolerance
def test_inviscid_mom_flux_components(actx_factory, dim, livedim): r"""Test components of the momentum flux with constant pressure, V != 0. Checks that the flux terms are returned in the proper order by running only 1 non-zero velocity component at-a-time. """ actx = actx_factory() eos = IdealSingleGas() p0 = 1.0 nel_1d = 4 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) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) tolerance = 1e-15 for livedim in range(dim): mass = discr.zeros(actx) + 1.0 + np.dot(nodes, nodes) mom = make_obj_array([discr.zeros(actx) for _ in range(dim)]) mom[livedim] = mass p_exact = discr.zeros(actx) + p0 energy = (p_exact / (eos.gamma() - 1.0) + 0.5 * np.dot(mom, mom) / mass) cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) p = eos.pressure(cv) from mirgecom.gas_model import GasModel, make_fluid_state state = make_fluid_state(cv, GasModel(eos=eos)) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) assert inf_norm(p - p_exact) < tolerance flux = inviscid_flux(state) logger.info(f"{dim}d flux = {flux}") vel_exact = mom / mass # first two components should be nonzero in livedim only assert inf_norm(flux.mass - mom) == 0 eflux_exact = (energy + p_exact) * vel_exact assert inf_norm(flux.energy - eflux_exact) == 0 logger.info("Testing momentum") xpmomflux = mass * np.outer(vel_exact, vel_exact) + p_exact * np.identity(dim) assert inf_norm(flux.momentum - xpmomflux) < tolerance
def test_viscous_timestep(actx_factory, dim, mu, vel): """Test timestep size.""" actx = actx_factory() nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(1.0, ) * dim, b=(2.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 1 discr = EagerDGDiscretization(actx, mesh, order=order) zeros = discr.zeros(actx) ones = zeros + 1.0 velocity = make_obj_array([zeros + vel for _ in range(dim)]) massval = 1 mass = massval * ones # I *think* this energy should yield c=1.0 energy = zeros + 1.0 / (1.4 * .4) mom = mass * velocity species_mass = None cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass) from grudge.dt_utils import characteristic_lengthscales chlen = characteristic_lengthscales(actx, discr) from grudge.op import nodal_min chlen_min = nodal_min(discr, "vol", chlen) mu = mu * chlen_min if mu < 0: mu = 0 tv_model = None else: tv_model = SimpleTransport(viscosity=mu) eos = IdealSingleGas() gas_model = GasModel(eos=eos, transport=tv_model) fluid_state = make_fluid_state(cv, gas_model) from mirgecom.viscous import get_viscous_timestep dt_field = get_viscous_timestep(discr, fluid_state) speed_total = fluid_state.wavespeed dt_expected = chlen / (speed_total + (mu / chlen)) error = (dt_expected - dt_field) / dt_expected assert actx.to_numpy(discr.norm(error, np.inf)) == 0
def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 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, n=(nel_1d,)*dim) order = 3 if dim == 2: # no deep meaning here, just a fudge factor dt = 0.75/(nel_1d*order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.45/(nel_1d*order**2) else: raise ValueError("don't have a stable time step guesstimate") print("%d elements" % mesh.nelements) discr = EagerDGDiscretization(actx, mesh, order=order) fields = flat_obj_array( bump(discr, actx), [discr.zeros(actx) for i in range(discr.dim)] ) vis = make_visualizer(discr, order+3 if dim == 2 else order) def rhs(t, w): return wave_operator(discr, c=1, w=w) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: print(f"step: {istep} t: {t} L2: {discr.norm(fields[0])} " f"sol max: {discr.nodal_max('vol', fields[0])}") vis.write_vtk_file("fld-wave-eager-%04d.vtu" % istep, [ ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def test_velocity_gradient_eoc(actx_factory, dim): """Test that the velocity gradient converges at the proper rate.""" from mirgecom.fluid import velocity_gradient actx = actx_factory() order = 3 from pytools.convergence import EOCRecorder eoc = EOCRecorder() nel_1d_0 = 4 for hn1 in [1, 2, 3, 4]: nel_1d = hn1 * nel_1d_0 h = 1/nel_1d from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(1.0,) * dim, b=(2.0,) * dim, nelements_per_axis=(nel_1d,) * dim ) discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) energy = zeros + 2.5 mass = nodes[dim-1]*nodes[dim-1] velocity = make_obj_array([actx.np.cos(nodes[i]) for i in range(dim)]) mom = mass*velocity q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) grad_q = obj_array_vectorize(discr.grad, q) grad_cv = split_conserved(dim, grad_q) grad_v = velocity_gradient(discr, cv, grad_cv) def exact_grad_row(xdata, gdim, dim): exact_grad_row = make_obj_array([zeros for _ in range(dim)]) exact_grad_row[gdim] = -actx.np.sin(xdata) return exact_grad_row comp_err = make_obj_array([ discr.norm(grad_v[i] - exact_grad_row(nodes[i], i, dim), np.inf) for i in range(dim)]) err_max = comp_err.max() eoc.add_data_point(h, err_max) logger.info(eoc) assert ( eoc.order_estimate() >= order - 0.5 or eoc.max_error() < 1e-9 )
def test_viscous_stress_tensor(actx_factory, transport_model): """Test tau data structure and values against exact.""" actx = actx_factory() dim = 3 nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(1.0, ) * dim, b=(2.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 1 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) ones = zeros + 1.0 # assemble velocities for simple, unique grad components velocity_x = nodes[0] + 2 * nodes[1] + 3 * nodes[2] velocity_y = 4 * nodes[0] + 5 * nodes[1] + 6 * nodes[2] velocity_z = 7 * nodes[0] + 8 * nodes[1] + 9 * nodes[2] velocity = make_obj_array([velocity_x, velocity_y, velocity_z]) mass = 2 * ones energy = zeros + 2.5 mom = mass * velocity cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) grad_cv = op.local_grad(discr, cv) if transport_model: tv_model = SimpleTransport(bulk_viscosity=1.0, viscosity=0.5) else: tv_model = PowerLawTransport() eos = IdealSingleGas() gas_model = GasModel(eos=eos, transport=tv_model) fluid_state = make_fluid_state(cv, gas_model) mu = tv_model.viscosity(eos, cv) lam = tv_model.volume_viscosity(eos, cv) # Exact answer for tau exp_grad_v = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) exp_grad_v_t = np.array([[1, 4, 7], [2, 5, 8], [3, 6, 9]]) exp_div_v = 15 exp_tau = (mu * (exp_grad_v + exp_grad_v_t) + lam * exp_div_v * np.eye(3)) from mirgecom.viscous import viscous_stress_tensor tau = viscous_stress_tensor(fluid_state, grad_cv) # The errors come from grad_v assert actx.to_numpy(discr.norm(tau - exp_tau, np.inf)) < 1e-12
def test_species_mass_gradient(actx_factory, dim): """Test gradY structure and values against exact.""" actx = actx_factory() nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(1.0,) * dim, b=(2.0,) * dim, nelements_per_axis=(nel_1d,) * dim ) order = 1 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) ones = zeros + 1 nspecies = 2*dim mass = 2*ones # make mass != 1 energy = zeros + 2.5 velocity = make_obj_array([ones for _ in range(dim)]) mom = mass * velocity # assemble y so that each one has simple, but unique grad components y = make_obj_array([ones for _ in range(nspecies)]) for idim in range(dim): ispec = 2*idim y[ispec] = ispec*(idim*dim+1)*sum([(iidim+1)*nodes[iidim] for iidim in range(dim)]) y[ispec+1] = -y[ispec] species_mass = mass*y cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass) from grudge.op import local_grad grad_cv = local_grad(discr, cv) from mirgecom.fluid import species_mass_fraction_gradient grad_y = species_mass_fraction_gradient(cv, grad_cv) assert grad_y.shape == (nspecies, dim) from meshmode.dof_array import DOFArray assert type(grad_y[0, 0]) == DOFArray def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) tol = 1e-11 for idim in range(dim): ispec = 2*idim exact_grad = np.array([(ispec*(idim*dim+1))*(iidim+1) for iidim in range(dim)]) assert inf_norm(grad_y[ispec] - exact_grad) < tol assert inf_norm(grad_y[ispec+1] + exact_grad) < tol
def test_local_max_species_diffusivity(actx_factory, dim, array_valued): """Test the local maximum species diffusivity.""" actx = actx_factory() nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(1.0, ) * dim, b=(2.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 1 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) ones = zeros + 1.0 vel = .32 velocity = make_obj_array([zeros + vel for _ in range(dim)]) massval = 1 mass = massval * ones energy = zeros + 1.0 / (1.4 * .4) mom = mass * velocity species_mass = np.array([1., 2., 3.], dtype=object) cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass) d_alpha_input = np.array([.1, .2, .3]) if array_valued: f = 1 + 0.1 * actx.np.sin(nodes[0]) d_alpha_input *= f tv_model = SimpleTransport(species_diffusivity=d_alpha_input) eos = IdealSingleGas() d_alpha = tv_model.species_diffusivity(eos, cv) from mirgecom.viscous import get_local_max_species_diffusivity expected = .3 * ones if array_valued: expected *= f calculated = get_local_max_species_diffusivity(actx, d_alpha) assert actx.to_numpy(discr.norm(calculated - expected, np.inf)) == 0
def test_velocity_gradient_structure(actx_factory): """Test gradv data structure, verifying usability with other helper routines.""" from mirgecom.fluid import velocity_gradient actx = actx_factory() dim = 3 nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(1.0,) * dim, b=(2.0,) * dim, nelements_per_axis=(nel_1d,) * dim ) order = 1 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) ones = zeros + 1.0 mass = 2*ones energy = zeros + 2.5 velocity_x = nodes[0] + 2*nodes[1] + 3*nodes[2] velocity_y = 4*nodes[0] + 5*nodes[1] + 6*nodes[2] velocity_z = 7*nodes[0] + 8*nodes[1] + 9*nodes[2] velocity = make_obj_array([velocity_x, velocity_y, velocity_z]) mom = mass * velocity cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) from grudge.op import local_grad grad_cv = local_grad(discr, cv) grad_v = velocity_gradient(cv, grad_cv) tol = 1e-11 exp_result = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] exp_trans = [[1, 4, 7], [2, 5, 8], [3, 6, 9]] exp_trace = 15 assert grad_v.shape == (dim, dim) from meshmode.dof_array import DOFArray assert type(grad_v[0, 0]) == DOFArray def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) assert inf_norm(grad_v - exp_result) < tol assert inf_norm(grad_v.T - exp_trans) < tol assert inf_norm(np.trace(grad_v) - exp_trace) < tol
def test_velocity_gradient_sanity(actx_factory, dim, mass_exp, vel_fac): """Test that the grad(v) returns {0, I} for v={constant, r_xyz}.""" from mirgecom.fluid import velocity_gradient actx = actx_factory() nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(1.0, ) * dim, b=(2.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) zeros = discr.zeros(actx) ones = zeros + 1.0 mass = 1 * ones for i in range(mass_exp): mass *= (mass + i) energy = zeros + 2.5 velocity = vel_fac * nodes mom = mass * velocity cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) from grudge.op import local_grad grad_cv = make_conserved(dim, q=local_grad(discr, cv.join())) grad_v = velocity_gradient(discr, cv, grad_cv) tol = 1e-11 exp_result = vel_fac * np.eye(dim) * ones grad_v_err = [ discr.norm(grad_v[i] - exp_result[i], np.inf) for i in range(dim) ] assert max(grad_v_err) < tol
def test_analytic_comparison(actx_factory): """Quick test of state comparison routine.""" from mirgecom.initializers import Vortex2D from mirgecom.simutil import compare_fluid_solutions, componentwise_norms actx = actx_factory() nel_1d = 4 dim = 2 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(1.0, ) * dim, b=(2.0, ) * dim, nelements_per_axis=(nel_1d, ) * dim) order = 2 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(discr.nodes(), actx) zeros = discr.zeros(actx) ones = zeros + 1.0 mass = ones energy = ones velocity = 2 * nodes mom = mass * velocity vortex_init = Vortex2D() vortex_soln = vortex_init(x_vec=nodes, eos=IdealSingleGas()) cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) resid = vortex_soln - cv expected_errors = actx.to_numpy( flatten(componentwise_norms(discr, resid, order=np.inf), actx)).tolist() errors = compare_fluid_solutions(discr, cv, cv) assert max(errors) == 0 errors = compare_fluid_solutions(discr, cv, vortex_soln) assert errors == expected_errors
def test_facial_flux(actx_factory, nspecies, order, dim): """Check the flux across element faces. The flux is checked by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_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 cv = make_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) from grudge.trace_pair import interior_trace_pairs cv_interior_pairs = interior_trace_pairs(discr, cv) # Check the boundary facial fluxes as called on an interior boundary # eos = IdealSingleGas() from mirgecom.gas_model import (GasModel, make_fluid_state) gas_model = GasModel(eos=IdealSingleGas()) from mirgecom.gas_model import make_fluid_state_trace_pairs state_tpairs = make_fluid_state_trace_pairs(cv_interior_pairs, gas_model) interior_state_pair = state_tpairs[0] from mirgecom.inviscid import inviscid_facial_flux interior_face_flux = \ inviscid_facial_flux(discr, state_tpair=interior_state_pair) def inf_norm(data): if len(data) > 0: return actx.to_numpy(discr.norm(data, np.inf, dd="all_faces")) else: return 0.0 assert inf_norm(interior_face_flux.mass) < tolerance assert inf_norm(interior_face_flux.energy) < tolerance assert inf_norm(interior_face_flux.species_mass) < tolerance # The expected pressure is 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) nhat = thaw(actx, discr.normal("int_faces")) mom_flux_exact = discr.project("int_faces", "all_faces", p0 * nhat) print(f"{mom_flux_exact=}") print(f"{interior_face_flux.momentum=}") momerr = inf_norm(interior_face_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a domain boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_mf = discr.project("vol", BTAG_ALL, species_mass_input) dir_bc = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) dir_bval = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) state_tpair = TracePair(BTAG_ALL, interior=make_fluid_state(dir_bval, gas_model), exterior=make_fluid_state(dir_bc, gas_model)) boundary_flux = inviscid_facial_flux(discr, state_tpair=state_tpair) assert inf_norm(boundary_flux.mass) < tolerance assert inf_norm(boundary_flux.energy) < tolerance assert inf_norm(boundary_flux.species_mass) < tolerance nhat = thaw(actx, discr.normal(BTAG_ALL)) mom_flux_exact = discr.project(BTAG_ALL, "all_faces", p0 * nhat) momerr = inf_norm(boundary_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) logger.info(f"standalone Errors:\n{eoc_rec0}" f"boundary 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 main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) from mpi4py import MPI comm = MPI.COMM_WORLD num_parts = comm.Get_size() from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim, boundary_tag_to_face={ "dirichlet": ["+x", "-x"], "neumann": ["+y", "-y"] }) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) if dim == 2: # no deep meaning here, just a fudge factor dt = 0.0025 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") source_width = 0.2 nodes = thaw(actx, discr.nodes()) u = discr.zeros(actx) vis = make_visualizer(discr, order + 3 if dim == 2 else order) boundaries = { grudge_sym.DTAG_BOUNDARY("dirichlet"): DirichletDiffusionBoundary(0.), grudge_sym.DTAG_BOUNDARY("neumann"): NeumannDiffusionBoundary(0.) } def rhs(t, u): return ( diffusion_operator(discr, alpha=1, boundaries=boundaries, u=u) + actx.np.exp(-np.dot(nodes, nodes) / source_width**2)) rank = comm.Get_rank() t = 0 t_final = 0.01 istep = 0 while True: if istep % 10 == 0: print(istep, t, discr.norm(u)) vis.write_vtk_file( "fld-heat-source-mpi-%03d-%04d.vtu" % (rank, istep), [("u", u)]) if t >= t_final: break u = rk4_step(u, t, dt, rhs) t += dt istep += 1
def test_uniform_rhs(actx_factory, dim, order): """Tests the inviscid rhs using a trivial constant/uniform state which should yield rhs = 0 to FP. The test is performed for 1, 2, and 3 dimensions. """ actx = actx_factory() tolerance = 1e-9 maxxerr = 0.0 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, n=(nel_1d, ) * dim) logger.info(f"Number of {dim}d elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) 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)]) fields = join_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input) expected_rhs = make_obj_array( [discr.zeros(actx) for i in range(len(fields))]) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = inviscid_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, q=fields, t=0.0) rhs_resid = inviscid_rhs - expected_rhs resid_split = split_conserved(dim, rhs_resid) rho_resid = resid_split.mass rhoe_resid = resid_split.energy mom_resid = resid_split.momentum rhs_split = split_conserved(dim, inviscid_rhs) rho_rhs = rhs_split.mass rhoe_rhs = rhs_split.energy rhov_rhs = rhs_split.momentum message = (f"rho_rhs = {rho_rhs}\n" f"rhoe_rhs = {rhoe_rhs}\n" f"rhov_rhs = {rhov_rhs}") logger.info(message) assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance err_max = discr.norm(rhs_resid[i], np.inf) eoc_rec0.add_data_point(1.0 / nel_1d, err_max) assert (err_max < tolerance) if err_max > maxxerr: maxxerr = 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 boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = inviscid_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, q=fields, t=0.0) rhs_resid = inviscid_rhs - expected_rhs resid_split = split_conserved(dim, rhs_resid) rho_resid = resid_split.mass rhoe_resid = resid_split.energy mom_resid = resid_split.momentum assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance err_max = discr.norm(rhs_resid[i], np.inf) eoc_rec1.add_data_point(1.0 / nel_1d, err_max) assert (err_max < tolerance) if err_max > maxxerr: maxxerr = err_max message = (f"V == 0 Errors:\n{eoc_rec0}" f"V != 0 Errors:\n{eoc_rec1}") print(message) 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 test_basic_cfd_healthcheck(actx_factory): """Quick test of some health checking utilities.""" actx = actx_factory() nel_1d = 4 dim = 2 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh( a=(1.0,) * dim, b=(2.0,) * dim, nelements_per_axis=(nel_1d,) * dim ) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(discr.nodes(), actx) zeros = discr.zeros(actx) ones = zeros + 1.0 # Let's make a very bad state (negative mass) mass = -1*ones velocity = 2 * nodes mom = mass * velocity energy = zeros + .5*np.dot(mom, mom)/mass eos = IdealSingleGas() cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) pressure = eos.pressure(cv) from mirgecom.simutil import check_range_local assert check_range_local(discr, "vol", mass, min_value=0, max_value=np.inf) assert check_range_local(discr, "vol", pressure, min_value=1e-6, max_value=np.inf) # Let's make another very bad state (nans) mass = 1*ones energy = zeros + 2.5 velocity = np.nan * nodes mom = mass * velocity cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) pressure = eos.pressure(cv) from mirgecom.simutil import check_naninf_local assert check_naninf_local(discr, "vol", pressure) # Let's make one last very bad state (inf) mass = 1*ones energy = np.inf * ones velocity = 2 * nodes mom = mass * velocity cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) pressure = eos.pressure(cv) assert check_naninf_local(discr, "vol", pressure) # What the hey, test a good one energy = 2.5 + .5*np.dot(mom, mom) cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom) pressure = eos.pressure(cv) assert not check_naninf_local(discr, "vol", pressure) assert not check_range_local(discr, "vol", pressure, min_value=0, max_value=np.inf)
def main(snapshot_pattern="wave-eager-{step:04d}-{rank:04d}.pkl", restart_step=None): """Drive the example.""" cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() num_parts = comm.Get_size() if restart_step is None: from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): 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) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() fields = None else: from mirgecom.restart import read_restart_data restart_data = read_restart_data( actx, snapshot_pattern.format(step=restart_step, rank=rank)) local_mesh = restart_data["local_mesh"] nel_1d = restart_data["nel_1d"] assert comm.Get_size() == restart_data["num_parts"] order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) if local_mesh.dim == 2: # no deep meaning here, just a fudge factor dt = 0.7 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.4 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") t_final = 3 if restart_step is None: t = 0 istep = 0 fields = flat_obj_array(bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)]) else: t = restart_data["t"] istep = restart_step assert istep == restart_step restart_fields = restart_data["fields"] old_order = restart_data["order"] if old_order != order: old_discr = EagerDGDiscretization(actx, local_mesh, order=old_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")) fields = connection(restart_fields) else: fields = restart_fields vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=1, w=w) while t < t_final: # restart must happen at beginning of step if istep % 100 == 0 and ( # Do not overwrite the restart file that we just read. istep != restart_step): from mirgecom.restart import write_restart_file write_restart_file(actx, restart_data={ "local_mesh": local_mesh, "order": order, "fields": fields, "t": t, "step": istep, "nel_1d": nel_1d, "num_parts": num_parts }, filename=snapshot_pattern.format(step=istep, rank=rank), comm=comm) if istep % 10 == 0: print(istep, t, discr.norm(fields[0])) vis.write_parallel_vtk_file( comm, "fld-wave-eager-mpi-%03d-%04d.vtu" % (rank, istep), [ ("u", fields[0]), ("v", fields[1:]), ]) fields = rk4_step(fields, t, dt, rhs) t += dt istep += 1
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 main(use_profiling=False, use_logmgr=False, lazy_eval: bool = False): """Drive the example.""" cl_ctx = cl.create_some_context() logmgr = initialize_logmgr(use_logmgr, filename="wave.sqlite", mode="wu") if use_profiling: if lazy_eval: raise RuntimeError("Cannot run lazy with profiling.") queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) actx = PyOpenCLProfilingArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) else: queue = cl.CommandQueue(cl_ctx) if lazy_eval: actx = PytatoPyOpenCLArrayContext(queue) else: actx = PyOpenCLArrayContext( queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) dim = 2 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) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) current_cfl = 0.485 wave_speed = 1.0 from grudge.dt_utils import characteristic_lengthscales nodal_dt = characteristic_lengthscales(actx, discr) / wave_speed from grudge.op import nodal_min dt = actx.to_numpy(current_cfl * nodal_min(discr, "vol", nodal_dt))[()] print("%d elements" % mesh.nelements) fields = flat_obj_array(bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)]) if logmgr: logmgr_add_cl_device_info(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) logmgr.add_watches(["step.max", "t_step.max", "t_log.max"]) try: logmgr.add_watches( ["memory_usage_python.max", "memory_usage_gpu.max"]) except KeyError: pass if use_profiling: logmgr.add_watches(["multiply_time.max"]) vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=wave_speed, w=w) compiled_rhs = actx.compile(rhs) t = 0 t_final = 1 istep = 0 while t < t_final: if logmgr: logmgr.tick_before() fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) if istep % 10 == 0: if use_profiling: print(actx.tabulate_profiling_data()) print(istep, t, actx.to_numpy(discr.norm(fields[0], np.inf))) vis.write_vtk_file("fld-wave-%04d.vtu" % istep, [ ("u", fields[0]), ("v", fields[1:]), ], overwrite=True) t += dt istep += 1 if logmgr: set_dt(logmgr, dt) logmgr.tick_after()
def main(snapshot_pattern="wave-mpi-{step:04d}-{rank:04d}.pkl", restart_step=None, use_profiling=False, use_logmgr=False, actx_class=PyOpenCLArrayContext): """Drive the example.""" cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() num_parts = comm.Get_size() logmgr = initialize_logmgr(use_logmgr, filename="wave-mpi.sqlite", mode="wu", mpi_comm=comm) if use_profiling: queue = cl.CommandQueue(cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) actx = actx_class(queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue)), logmgr=logmgr) else: queue = cl.CommandQueue(cl_ctx) actx = actx_class(queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) if restart_step is None: from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): 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) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() fields = None else: from mirgecom.restart import read_restart_data restart_data = read_restart_data( actx, snapshot_pattern.format(step=restart_step, rank=rank) ) local_mesh = restart_data["local_mesh"] nel_1d = restart_data["nel_1d"] assert comm.Get_size() == restart_data["num_parts"] order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) current_cfl = 0.485 wave_speed = 1.0 from grudge.dt_utils import characteristic_lengthscales dt = current_cfl * characteristic_lengthscales(actx, discr) / wave_speed from grudge.op import nodal_min dt = nodal_min(discr, "vol", dt) t_final = 1 if restart_step is None: t = 0 istep = 0 fields = flat_obj_array( bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)] ) else: t = restart_data["t"] istep = restart_step assert istep == restart_step restart_fields = restart_data["fields"] old_order = restart_data["order"] if old_order != order: old_discr = EagerDGDiscretization(actx, local_mesh, order=old_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")) fields = connection(restart_fields) else: fields = restart_fields if logmgr: logmgr_add_cl_device_info(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) logmgr.add_watches(["step.max", "t_step.max", "t_log.max"]) try: logmgr.add_watches(["memory_usage_python.max", "memory_usage_gpu.max"]) except KeyError: pass if use_profiling: logmgr.add_watches(["multiply_time.max"]) vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=wave_speed, w=w) compiled_rhs = actx.compile(rhs) while t < t_final: if logmgr: logmgr.tick_before() # restart must happen at beginning of step if istep % 100 == 0 and ( # Do not overwrite the restart file that we just read. istep != restart_step): from mirgecom.restart import write_restart_file write_restart_file( actx, restart_data={ "local_mesh": local_mesh, "order": order, "fields": fields, "t": t, "step": istep, "nel_1d": nel_1d, "num_parts": num_parts}, filename=snapshot_pattern.format(step=istep, rank=rank), comm=comm ) if istep % 10 == 0: print(istep, t, discr.norm(fields[0])) vis.write_parallel_vtk_file( comm, "fld-wave-mpi-%03d-%04d.vtu" % (rank, istep), [ ("u", fields[0]), ("v", fields[1:]), ], overwrite=True ) fields = thaw(freeze(fields, actx), actx) fields = rk4_step(fields, t, dt, compiled_rhs) t += dt istep += 1 if logmgr: set_dt(logmgr, dt) logmgr.tick_after() final_soln = discr.norm(fields[0]) assert np.abs(final_soln - 0.04409852463947439) < 1e-14
def main(use_profiling=False): """Drive the example.""" cl_ctx = cl.create_some_context() if use_profiling: queue = cl.CommandQueue( cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) actx = PyOpenCLProfilingArrayContext( queue, allocator=cl_tools.MemoryPool(cl_tools.ImmediateAllocator(queue))) else: queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) dim = 2 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) order = 3 if dim == 2: # no deep meaning here, just a fudge factor dt = 0.7 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.4 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") print("%d elements" % mesh.nelements) discr = EagerDGDiscretization(actx, mesh, order=order) fields = flat_obj_array(bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)]) vis = make_visualizer(discr) def rhs(t, w): return wave_operator(discr, c=1, w=w) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: if use_profiling: print(actx.tabulate_profiling_data()) print(istep, t, discr.norm(fields[0], np.inf)) vis.write_vtk_file("fld-wave-eager-%04d.vtu" % istep, [ ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def test_diffusion_compare_to_nodal_dg(actx_factory, problem, order, print_err=False): """Compares diffusion operator to Hesthaven/Warburton Nodal-DG code.""" pytest.importorskip("oct2py") actx = actx_factory() p = problem assert p.dim == 1 assert p.sym_alpha == 1. from meshmode.interop.nodal_dg import download_nodal_dg_if_not_present download_nodal_dg_if_not_present() sym_diffusion_u = sym_diffusion(p.dim, p.sym_alpha, p.sym_u) for n in [4, 8, 16, 32, 64]: mesh = p.get_mesh(n) from meshmode.interop.nodal_dg import NodalDGContext with NodalDGContext("./nodal-dg/Codes1.1") as ndgctx: ndgctx.set_mesh(mesh, order=order) t = 1.23456789 from grudge.eager import EagerDGDiscretization discr_mirgecom = EagerDGDiscretization(actx, mesh, order=order) nodes_mirgecom = thaw(actx, discr_mirgecom.nodes()) def sym_eval_mirgecom(expr): return sym.EvaluationMapper({ "x": nodes_mirgecom, "t": t })(expr) u_mirgecom = sym_eval_mirgecom(p.sym_u) diffusion_u_mirgecom = diffusion_operator( discr_mirgecom, quad_tag=DISCR_TAG_BASE, alpha=discr_mirgecom.zeros(actx) + 1., boundaries=p.get_boundaries(discr_mirgecom, actx, t), u=u_mirgecom) discr_ndg = ndgctx.get_discr(actx) nodes_ndg = thaw(actx, discr_ndg.nodes()) def sym_eval_ndg(expr): return sym.EvaluationMapper({"x": nodes_ndg, "t": t})(expr) u_ndg = sym_eval_ndg(p.sym_u) ndgctx.push_dof_array("u", u_ndg) ndgctx.octave.push("t", t) ndgctx.octave.eval("[rhs] = HeatCRHS1D(u,t)", verbose=False) diffusion_u_ndg = ndgctx.pull_dof_array(actx, "rhs") def inf_norm(f): return actx.np.linalg.norm(f, np.inf) err = (inf_norm(diffusion_u_mirgecom - diffusion_u_ndg) / inf_norm(diffusion_u_ndg)) if print_err: diffusion_u_exact = sym_eval_mirgecom(sym_diffusion_u) err_exact = ( inf_norm(diffusion_u_mirgecom - diffusion_u_exact) / inf_norm(diffusion_u_exact)) print(err, err_exact) assert err < 1e-9
def test_uniform_rhs(actx_factory, nspecies, dim, order): """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}") discr = EagerDGDiscretization(actx, mesh, order=order) 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) 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, eos=IdealSingleGas(), boundaries=boundaries, cv=cv, 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 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") assert discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance for i in range(nspecies): assert discr.norm(rhoy_resid[i], np.inf) < tolerance err_max = discr.norm(rho_resid, np.inf) 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) boundaries = {BTAG_ALL: DummyBoundary()} inviscid_rhs = euler_operator(discr, eos=IdealSingleGas(), boundaries=boundaries, cv=cv, 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 discr.norm(rho_resid, np.inf) < tolerance assert discr.norm(rhoe_resid, np.inf) < tolerance for i in range(dim): assert discr.norm(mom_resid[i], np.inf) < tolerance for i in range(nspecies): assert discr.norm(rhoy_resid[i], np.inf) < tolerance err_max = discr.norm(rho_resid, np.inf) 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 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 main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 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, n=(nel_1d, ) * dim) order = 3 if dim == 2: # no deep meaning here, just a fudge factor dt = 0.75 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.45 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") print("%d elements" % mesh.nelements) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory, \ PolynomialWarpAndBlendGroupFactory discr = EagerDGDiscretization( actx, mesh, quad_tag_to_group_factory={ QTAG_NONE: PolynomialWarpAndBlendGroupFactory(order), "vel_prod": QuadratureSimplexGroupFactory(3 * order), }) # bounded above by 1 c = 0.2 + 0.8 * bump(actx, discr, center=np.zeros(3), width=0.5) fields = flat_obj_array(bump( actx, discr, ), [discr.zeros(actx) for i in range(discr.dim)]) vis = make_visualizer(discr, order + 3 if dim == 2 else order) def rhs(t, w): return wave_operator(discr, c=c, w=w) t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: print(istep, t, discr.norm(fields[0])) vis.write_vtk_file("fld-wave-eager-var-velocity-%04d.vtu" % istep, [ ("c", c), ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def test_facial_flux(actx_factory, order, dim): """Check the flux across element faces by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) fields = join_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input) from mirgecom.euler import _facial_flux interior_face_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=interior_trace_pair( discr, fields)) from functools import partial fnorm = partial(discr.norm, p=np.inf, dd="all_faces") iff_split = split_conserved(dim, interior_face_flux) assert fnorm(iff_split.mass) < tolerance assert fnorm(iff_split.energy) < tolerance # The expected pressure 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) momerr = fnorm(iff_split.momentum) - p0 assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_bval = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) dir_bc = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) boundary_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=TracePair(BTAG_ALL, interior=dir_bval, exterior=dir_bc)) bf_split = split_conserved(dim, boundary_flux) assert fnorm(bf_split.mass) < tolerance assert fnorm(bf_split.energy) < tolerance momerr = fnorm(bf_split.momentum) - p0 assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) message = (f"standalone Errors:\n{eoc_rec0}" f"boundary Errors:\n{eoc_rec1}") logger.info(message) 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 test_pyrometheus_eos(ctx_factory, mechname, dim, y0, vel): """Test PyrometheusMixture EOS for all available mechanisms. Tests that the PyrometheusMixture EOS gets the same thermo properties (p, T, e) as the Pyrometheus-native mechanism code. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) nel_1d = 4 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) order = 4 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) # Pyrometheus initialization mech_cti = get_mechanism_cti(mechname) sol = cantera.Solution(phase_id="gas", source=mech_cti) prometheus_mechanism = pyro.get_thermochem_class(sol)(actx.np) nspecies = prometheus_mechanism.num_species print(f"PrometheusMixture::Mechanism = {mechname}") print(f"PrometheusMixture::NumSpecies = {nspecies}") press0 = 101500.0 temp0 = 300.0 y0s = np.zeros(shape=(nspecies, )) for i in range(1, nspecies): y0s[i] = y0 / (10.0**i) y0s[0] = 1.0 - np.sum(y0s[1:]) velocity = vel * np.ones(shape=(dim, )) for fac in range(1, 11): tempin = fac * temp0 pressin = fac * press0 print(f"Testing {mechname}(t,P) = ({tempin}, {pressin})") ones = discr.zeros(actx) + 1.0 tin = tempin * ones pin = pressin * ones yin = y0s * ones tguess = 300.0 pyro_rho = prometheus_mechanism.get_density(pin, tin, yin) pyro_e = prometheus_mechanism.get_mixture_internal_energy_mass( tin, yin) pyro_t = prometheus_mechanism.get_temperature(pyro_e, tguess, yin, True) pyro_p = prometheus_mechanism.get_pressure(pyro_rho, pyro_t, yin) print(f"prom(rho, y, p, t, e) = ({pyro_rho}, {y0s}, " f"{pyro_p}, {pyro_t}, {pyro_e})") eos = PyrometheusMixture(prometheus_mechanism) initializer = MixtureInitializer(dim=dim, nspecies=nspecies, pressure=pyro_p, temperature=pyro_t, massfractions=y0s, velocity=velocity) cv = initializer(eos=eos, t=0, x_vec=nodes) p = eos.pressure(cv) temperature = eos.temperature(cv) internal_energy = eos.get_internal_energy(tin, yin) y = eos.species_fractions(cv) print(f"pyro_y = {y}") print(f"pyro_eos.p = {p}") print(f"pyro_eos.temp = {temperature}") print(f"pyro_eos.e = {internal_energy}") tol = 1e-14 assert discr.norm((cv.mass - pyro_rho) / pyro_rho, np.inf) < tol assert discr.norm((temperature - pyro_t) / pyro_t, np.inf) < tol assert discr.norm((internal_energy - pyro_e) / pyro_e, np.inf) < tol assert discr.norm((p - pyro_p) / pyro_p, np.inf) < tol
def main(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, allocator=cl_tools.MemoryPool( cl_tools.ImmediateAllocator(queue))) comm = MPI.COMM_WORLD num_parts = comm.Get_size() from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) if dim == 2: # no deep meaning here, just a fudge factor dt = 0.75 / (nel_1d * order**2) elif dim == 3: # no deep meaning here, just a fudge factor dt = 0.45 / (nel_1d * order**2) else: raise ValueError("don't have a stable time step guesstimate") fields = flat_obj_array(bump(actx, discr), [discr.zeros(actx) for i in range(discr.dim)]) vis = make_visualizer(discr, order + 3 if dim == 2 else order) def rhs(t, w): return wave_operator(discr, c=1, w=w) rank = comm.Get_rank() t = 0 t_final = 3 istep = 0 while t < t_final: fields = rk4_step(fields, t, dt, rhs) if istep % 10 == 0: print(istep, t, discr.norm(fields[0])) vis.write_vtk_file( "fld-wave-eager-mpi-%03d-%04d.vtu" % (rank, istep), [ ("u", fields[0]), ("v", fields[1:]), ]) t += dt istep += 1
def test_pyrometheus_kinetics(ctx_factory, mechname, rate_tol, y0): """Test known pyrometheus reaction mechanisms. This test reproduces a pyrometheus-native test in the MIRGE context. Tests that the Pyrometheus mechanism code gets the same chemical properties and reaction rates as the corresponding mechanism in Cantera. The reactions are integrated in time and verified against a homogeneous reactor in Cantera. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 1 nel_1d = 4 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) order = 4 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) ones = discr.zeros(actx) + 1.0 # Pyrometheus initialization mech_cti = get_mechanism_cti(mechname) cantera_soln = cantera.Solution(phase_id="gas", source=mech_cti) pyro_obj = pyro.get_thermochem_class(cantera_soln)(actx.np) nspecies = pyro_obj.num_species print(f"PrometheusMixture::NumSpecies = {nspecies}") tempin = 1500.0 pressin = cantera.one_atm print(f"Testing (t,P) = ({tempin}, {pressin})") # Homogeneous reactor to get test data equiv_ratio = 1.0 ox_di_ratio = 0.21 stoich_ratio = 0.5 i_fu = cantera_soln.species_index("H2") i_ox = cantera_soln.species_index("O2") i_di = cantera_soln.species_index("N2") x = np.zeros(shape=(nspecies, )) 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 cantera_soln.TPX = tempin, pressin, x # cantera_soln.equilibrate("UV") can_t, can_rho, can_y = cantera_soln.TDY # can_p = cantera_soln.P reactor = cantera.IdealGasConstPressureReactor(cantera_soln) sim = cantera.ReactorNet([reactor]) time = 0.0 for _ in range(50): time += 1.0e-6 sim.advance(time) # Cantera kinetics can_r = reactor.kinetics.net_rates_of_progress can_omega = reactor.kinetics.net_production_rates # Get state from Cantera can_t = reactor.T can_rho = reactor.density can_y = reactor.Y print(f"can_y = {can_y}") tin = can_t * ones rhoin = can_rho * ones yin = can_y * ones # Prometheus kinetics pyro_c = pyro_obj.get_concentrations(rhoin, yin) print(f"pyro_conc = {pyro_c}") pyro_r = pyro_obj.get_net_rates_of_progress(tin, pyro_c) pyro_omega = pyro_obj.get_net_production_rates(rhoin, tin, yin) # Print print(f"can_r = {can_r}") print(f"pyro_r = {pyro_r}") abs_diff = discr.norm(pyro_r - can_r, np.inf) if abs_diff > 1e-14: min_r = (np.abs(can_r)).min() if min_r > 0: assert discr.norm((pyro_r - can_r) / can_r, np.inf) < rate_tol else: assert discr.norm(pyro_r, np.inf) < rate_tol print(f"can_omega = {can_omega}") print(f"pyro_omega = {pyro_omega}") for i, omega in enumerate(can_omega): omin = np.abs(omega).min() if omin > 1e-12: assert discr.norm( (pyro_omega[i] - omega) / omega, np.inf) < 1e-8 else: assert discr.norm(pyro_omega[i], np.inf) < 1e-12
def main(ctx_factory=cl.create_some_context, use_logmgr=True, use_leap=False, use_profiling=False, casename=None, rst_filename=None, actx_class=PyOpenCLArrayContext): """Run the example.""" cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) from mpi4py import MPI comm = MPI.COMM_WORLD num_parts = comm.Get_size() logmgr = initialize_logmgr(use_logmgr, filename="heat-source.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))) from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis mesh_dist = MPIMeshDistributor(comm) dim = 2 nel_1d = 16 t = 0 t_final = 0.0002 istep = 0 if mesh_dist.is_mananger_rank(): 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, boundary_tag_to_face={ "dirichlet": ["+x", "-x"], "neumann": ["+y", "-y"] } ) print("%d elements" % mesh.nelements) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) del mesh else: local_mesh = mesh_dist.receive_mesh_part() order = 3 discr = EagerDGDiscretization(actx, local_mesh, order=order, mpi_communicator=comm) if dim == 2: # no deep meaning here, just a fudge factor dt = 0.0025/(nel_1d*order**2) else: raise ValueError("don't have a stable time step guesstimate") source_width = 0.2 from arraycontext import thaw nodes = thaw(discr.nodes(), actx) boundaries = { DTAG_BOUNDARY("dirichlet"): DirichletDiffusionBoundary(0.), DTAG_BOUNDARY("neumann"): NeumannDiffusionBoundary(0.) } u = discr.zeros(actx) if logmgr: logmgr_add_device_name(logmgr, queue) logmgr_add_device_memory_usage(logmgr, queue) logmgr.add_watches(["step.max", "t_step.max", "t_log.max"]) try: logmgr.add_watches(["memory_usage_python.max", "memory_usage_gpu.max"]) except KeyError: pass if use_profiling: logmgr.add_watches(["multiply_time.max"]) vis_timer = IntervalTimer("t_vis", "Time spent visualizing") logmgr.add_quantity(vis_timer) vis = make_visualizer(discr) def rhs(t, u): return ( diffusion_operator( discr, quad_tag=DISCR_TAG_BASE, alpha=1, boundaries=boundaries, u=u) + actx.np.exp(-np.dot(nodes, nodes)/source_width**2)) compiled_rhs = actx.compile(rhs) rank = comm.Get_rank() while t < t_final: if logmgr: logmgr.tick_before() if istep % 10 == 0: print(istep, t, actx.to_numpy(actx.np.linalg.norm(u[0]))) vis.write_vtk_file("fld-heat-source-mpi-%03d-%04d.vtu" % (rank, istep), [ ("u", u) ], overwrite=True) u = rk4_step(u, t, dt, compiled_rhs) t += dt istep += 1 if logmgr: set_dt(logmgr, dt) logmgr.tick_after() final_answer = discr.norm(u, np.inf) resid = abs(final_answer - 0.00020620711665201585) if resid > 1e-15: raise ValueError(f"Run did not produce the expected result {resid=}")
def test_pyrometheus_mechanisms(ctx_factory, mechname, rate_tol, y0): """Test known pyrometheus mechanisms. This test reproduces a pyrometheus-native test in the MIRGE context. Tests that the Pyrometheus mechanism code gets the same thermo properties as the corresponding mechanism in Cantera. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 1 nel_1d = 2 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) order = 4 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) # Pyrometheus initialization mech_cti = get_mechanism_cti(mechname) sol = cantera.Solution(phase_id="gas", source=mech_cti) prometheus_mechanism = pyro.get_thermochem_class(sol)(actx.np) nspecies = prometheus_mechanism.num_species print(f"PyrometheusMixture::NumSpecies = {nspecies}") press0 = 101500.0 temp0 = 300.0 y0s = np.zeros(shape=(nspecies, )) for i in range(nspecies - 1): y0s[i] = y0 / (10.0**(i + 1)) y0s[-1] = 1.0 - np.sum(y0s[:-1]) for fac in range(1, 11): pressin = fac * press0 tempin = fac * temp0 print(f"Testing (t,P) = ({tempin}, {pressin})") cantera_soln = cantera.Solution(phase_id="gas", source=mech_cti) cantera_soln.TPY = tempin, pressin, y0s cantera_soln.equilibrate("UV") can_t, can_rho, can_y = cantera_soln.TDY can_p = cantera_soln.P can_e = cantera_soln.int_energy_mass can_k = cantera_soln.forward_rate_constants can_c = cantera_soln.concentrations # Chemistry functions for testing pyro chem can_r = cantera_soln.net_rates_of_progress can_omega = cantera_soln.net_production_rates ones = discr.zeros(actx) + 1.0 tin = can_t * ones pin = can_p * ones yin = make_obj_array([can_y[i] * ones for i in range(nspecies)]) prom_rho = prometheus_mechanism.get_density(pin, tin, yin) prom_e = prometheus_mechanism.get_mixture_internal_energy_mass( tin, yin) prom_t = prometheus_mechanism.get_temperature(prom_e, tin, yin, True) prom_p = prometheus_mechanism.get_pressure(prom_rho, tin, yin) prom_c = prometheus_mechanism.get_concentrations(prom_rho, yin) prom_k = prometheus_mechanism.get_fwd_rate_coefficients(prom_t, prom_c) # Pyro chemistry functions prom_r = prometheus_mechanism.get_net_rates_of_progress(prom_t, prom_c) prom_omega = prometheus_mechanism.get_net_production_rates( prom_rho, prom_t, yin) print(f"can(rho, y, p, t, e, k) = ({can_rho}, {can_y}, " f"{can_p}, {can_t}, {can_e}, {can_k})") print(f"prom(rho, y, p, t, e, k) = ({prom_rho}, {y0s}, " f"{prom_p}, {prom_t}, {prom_e}, {prom_k})") # For pyro chem testing print(f"can_r = {can_r}") print(f"prom_r = {prom_r}") print(f"can_omega = {can_omega}") print(f"prom_omega = {prom_omega}") assert discr.norm((prom_c - can_c) / can_c, np.inf) < 1e-14 assert discr.norm((prom_t - can_t) / can_t, np.inf) < 1e-14 assert discr.norm((prom_rho - can_rho) / can_rho, np.inf) < 1e-14 assert discr.norm((prom_p - can_p) / can_p, np.inf) < 1e-14 assert discr.norm((prom_e - can_e) / can_e, np.inf) < 1e-6 assert discr.norm((prom_k - can_k) / can_k, np.inf) < 1e-10 # Pyro chem test comparisons for i, rate in enumerate(can_r): assert discr.norm((prom_r[i] - rate), np.inf) < rate_tol for i, rate in enumerate(can_omega): assert discr.norm((prom_omega[i] - rate), np.inf) < rate_tol