def test_inviscid_flux(actx_factory, dim): """Identity test - directly check inviscid flux routine :func:`mirgecom.euler.inviscid_flux` against the exact expected result. This test is designed to fail if the flux routine is broken. The expected inviscid flux is: F(q) = <rhoV, (E+p)V, rho(V.x.V) + pI> """ actx = actx_factory() nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh # for dim in [1, 2, 3]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) order = 3 discr = EagerDGDiscretization(actx, mesh, order=order) eos = IdealSingleGas() logger.info(f"Number of {dim}d elems: {mesh.nelements}") mass = cl.clrandom.rand(actx.queue, (mesh.nelements, ), dtype=np.float64) energy = cl.clrandom.rand(actx.queue, (mesh.nelements, ), dtype=np.float64) mom = make_obj_array([ cl.clrandom.rand(actx.queue, (mesh.nelements, ), dtype=np.float64) for i in range(dim) ]) q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) # {{{ create the expected result p = eos.pressure(cv) escale = (energy + p) / mass expected_flux = np.zeros((dim + 2, dim), dtype=object) expected_flux[0] = mom expected_flux[1] = mom * make_obj_array([escale]) for i in range(dim): for j in range(dim): expected_flux[2 + i, j] = (mom[i] * mom[j] / mass + (p if i == j else 0)) # }}} from mirgecom.euler import inviscid_flux flux = inviscid_flux(discr, eos, q) flux_resid = flux - expected_flux for i in range(dim + 2, dim): for j in range(dim): assert (la.norm(flux_resid[i, j].get())) == 0.0
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_idealsingle_lump(ctx_factory): """Test EOS with mass lump. Tests that the IdealSingleGas EOS returns the correct (uniform) pressure for the Lump solution field. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=[(0.0, ), (-5.0, )], b=[(10.0, ), (5.0, )], n=(nel_1d, ) * dim) order = 3 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) # Init soln with Vortex center = np.zeros(shape=(dim, )) velocity = np.zeros(shape=(dim, )) center[0] = 5 velocity[0] = 1 lump = Lump(center=center, velocity=velocity) eos = IdealSingleGas() lump_soln = lump(0, nodes) cv = split_conserved(dim, lump_soln) p = eos.pressure(cv) exp_p = 1.0 errmax = discr.norm(p - exp_p, np.inf) exp_ke = 0.5 * cv.mass ke = eos.kinetic_energy(cv) kerr = discr.norm(ke - exp_ke, np.inf) te = eos.total_energy(cv, p) terr = discr.norm(te - cv.energy, np.inf) logger.info(f"lump_soln = {lump_soln}") logger.info(f"pressure = {p}") assert errmax < 1e-15 assert kerr < 1e-15 assert terr < 1e-15
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_idealsingle_lump(ctx_factory, dim): """Test IdealSingle EOS with mass lump. Tests that the IdealSingleGas EOS returns the correct (uniform) pressure for the Lump solution field. """ 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 = 3 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) from meshmode.dof_array import thaw nodes = thaw(actx, discr.nodes()) # Init soln with Vortex center = np.zeros(shape=(dim, )) velocity = np.zeros(shape=(dim, )) velocity[0] = 1 lump = Lump(dim=dim, center=center, velocity=velocity) eos = IdealSingleGas() cv = lump(nodes) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) p = eos.pressure(cv) exp_p = 1.0 errmax = inf_norm(p - exp_p) exp_ke = 0.5 * cv.mass ke = eos.kinetic_energy(cv) kerr = inf_norm(ke - exp_ke) te = eos.total_energy(cv, p) terr = inf_norm(te - cv.energy) logger.info(f"lump_soln = {cv}") logger.info(f"pressure = {p}") assert errmax < 1e-15 assert kerr < 1e-15 assert terr < 1e-15
def test_idealsingle_vortex(ctx_factory): r"""Test EOS with isentropic vortex. Tests that the IdealSingleGas EOS returns the correct pressure (p) for the Vortex2D solution field (i.e. $p = \rho^{\gamma}$). """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=[(0.0, ), (-5.0, )], b=[(10.0, ), (5.0, )], nelements_per_axis=(nel_1d, ) * dim) order = 3 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) from meshmode.dof_array import thaw nodes = thaw(actx, discr.nodes()) eos = IdealSingleGas() # Init soln with Vortex vortex = Vortex2D() cv = vortex(nodes) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) gamma = eos.gamma() p = eos.pressure(cv) exp_p = cv.mass**gamma errmax = inf_norm(p - exp_p) exp_ke = 0.5 * np.dot(cv.momentum, cv.momentum) / cv.mass ke = eos.kinetic_energy(cv) kerr = inf_norm(ke - exp_ke) te = eos.total_energy(cv, p) terr = inf_norm(te - cv.energy) logger.info(f"vortex_soln = {cv}") logger.info(f"pressure = {p}") assert errmax < 1e-15 assert kerr < 1e-15 assert terr < 1e-15
def test_uniform(ctx_factory, dim): """ Simple test to check that Uniform initializer creates the expected solution field. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) 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 = 1 print(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) print(f"DIM = {dim}, {len(nodes)}") print(f"Nodes={nodes}") from mirgecom.initializers import Uniform initr = Uniform(dim=dim) initsoln = initr(time=0.0, x_vec=nodes) tol = 1e-15 def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) assert inf_norm(initsoln.mass - 1.0) < tol assert inf_norm(initsoln.energy - 2.5) < tol print(f"Uniform Soln:{initsoln}") eos = IdealSingleGas() p = eos.pressure(initsoln) print(f"Press:{p}") assert inf_norm(p - 1.0) < tol
def test_idealsingle_vortex(ctx_factory): r""" Tests that the IdealSingleGas EOS returns the correct pressure (p) for the Vortex2D solution field (i.e. :math:'p = \rho^{\gamma}'). """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) dim = 2 nel_1d = 4 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=[(0.0, ), (-5.0, )], b=[(10.0, ), (5.0, )], n=(nel_1d, ) * dim) order = 3 logger.info(f"Number of elements {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) eos = IdealSingleGas() # Init soln with Vortex vortex = Vortex2D() vortex_soln = vortex(0, nodes) cv = split_conserved(dim, vortex_soln) gamma = eos.gamma() p = eos.pressure(cv) exp_p = cv.mass**gamma errmax = discr.norm(p - exp_p, np.inf) logger.info(f"vortex_soln = {vortex_soln}") logger.info(f"pressure = {p}") assert errmax < 1e-15
def test_shock_init(ctx_factory): """ Simple test to check that Shock1D initializer creates the expected solution field. """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) nel_1d = 10 dim = 2 from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=[(0.0, ), (1.0, )], b=[(-0.5, ), (0.5, )], nelements_per_axis=(nel_1d, ) * dim) order = 3 print(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) initr = SodShock1D() initsoln = initr(time=0.0, x_vec=nodes) print("Sod Soln:", initsoln) xpl = 1.0 xpr = 0.1 tol = 1e-15 nodes_x = nodes[0] eos = IdealSingleGas() p = eos.pressure(initsoln) assert actx.to_numpy( discr.norm(actx.np.where(nodes_x < 0.5, p - xpl, p - xpr), np.inf)) < tol
def test_inviscid_mom_flux_components(actx_factory, dim, livedim): r"""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 from mirgecom.euler import inviscid_flux tolerance = 1e-15 for livedim in range(dim): for ntestnodes in [1, 10]: discr = MyDiscr(dim, ntestnodes) mass = discr.zeros(actx) mass_values = np.empty((1, ntestnodes), dtype=np.float64) mass_values[0] = np.linspace(1., ntestnodes, ntestnodes, dtype=np.float64) mass[0].set(mass_values) mom = make_obj_array([discr.zeros(actx) for _ in range(dim)]) mom[livedim] = mass p = discr.zeros(actx) + p0 energy = (p / (eos.gamma() - 1.0) + 0.5 * np.dot(mom, mom) / mass) q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) p = eos.pressure(cv) flux = inviscid_flux(discr, eos, q) logger.info(f"{dim}d flux = {flux}") # first two components should be nonzero in livedim only expected_flux = mom logger.info("Testing continuity") for i in range(dim): assert la.norm((flux[0, i] - expected_flux[i])[0].get()) == 0.0 if i != livedim: assert la.norm(flux[0, i][0].get()) == 0.0 else: assert la.norm(flux[0, i][0].get()) > 0.0 logger.info("Testing energy") expected_flux = mom * (energy + p) / mass for i in range(dim): assert la.norm((flux[1, i] - expected_flux[i])[0].get()) == 0.0 if i != livedim: assert la.norm(flux[1, i][0].get()) == 0.0 else: assert la.norm(flux[1, i][0].get()) > 0.0 logger.info("Testing momentum") xpmomflux = make_obj_array([ (mom[i] * mom[j] / mass + (p if i == j else 0)) for i in range(dim) for j in range(dim) ]) for i in range(dim): expected_flux = xpmomflux[i * dim:(i + 1) * dim] for j in range(dim): assert la.norm( (flux[2 + i, j] - expected_flux[j])[0].get()) == 0 if i == j: if i == livedim: assert (la.norm(flux[2 + i, j][0].get()) > 0.0) else: # just for sanity - make sure the flux recovered the # prescribed value of p0 (within fp tol) assert (np.abs(flux[2 + i, j][0].get() - p0) < tolerance).all() else: assert la.norm(flux[2 + i, j][0].get()) == 0.0
def test_inviscid_flux_components(actx_factory, dim): """Uniform pressure test Checks that the Euler-internal inviscid flux routine :func:`mirgecom.euler.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() from mirgecom.euler import inviscid_flux p0 = 1.0 # === 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 for ntestnodes in [1, 10]: discr = MyDiscr(dim, ntestnodes) mass = discr.zeros(actx) mass_values = np.empty((1, ntestnodes), dtype=np.float64) mass_values[0] = np.linspace(1., ntestnodes, ntestnodes, dtype=np.float64) mass[0].set(mass_values) mom = make_obj_array([discr.zeros(actx) for _ in range(dim)]) p = discr.zeros(actx) + p0 energy = p / 0.4 + 0.5 * np.dot(mom, mom) / mass q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) p = eos.pressure(cv) flux = inviscid_flux(discr, eos, q) logger.info(f"{dim}d flux = {flux}") # for velocity zero, these components should be == zero for i in range(2): for j in range(dim): assert (flux[i, j][0].get() == 0.0).all() # The momentum diagonal should be p # Off-diagonal should be identically 0 for i in range(dim): for j in range(dim): print(f"(i,j) = ({i},{j})") if i != j: assert (flux[2 + i, j][0].get() == 0.0).all() else: assert (flux[2 + i, j] == p)[0].get().all() assert (np.abs(flux[2 + i, j][0].get() - p0) < tolerance).all()
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 test_inviscid_mom_flux_components(actx_factory, dim, livedim): r"""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. """ queue = actx_factory().queue eos = IdealSingleGas() class MyDiscr: def __init__(self, dim=1): self.dim = dim p0 = 1.0 from mirgecom.euler import inviscid_flux tolerance = 1e-15 for livedim in range(dim): for ntestnodes in [1, 10]: fake_dis = MyDiscr(dim) mass = cl.clrandom.rand(queue, (ntestnodes, ), dtype=np.float64) energy = cl.clrandom.rand(queue, (ntestnodes, ), dtype=np.float64) mom = make_obj_array([ cl.clrandom.rand(queue, (ntestnodes, ), dtype=np.float64) for i in range(dim) ]) p = cl.clrandom.rand(queue, (ntestnodes, ), dtype=np.float64) for i in range(ntestnodes): mass[i] = 1.0 + i p[i] = p0 for j in range(dim): mom[j][i] = 0.0 * mass[i] mom[livedim][i] = mass[i] energy = (p / (eos.gamma() - 1.0) + 0.5 * np.dot(mom, mom) / mass) q = join_conserved(dim, mass=mass, energy=energy, momentum=mom) cv = split_conserved(dim, q) p = eos.pressure(cv) flux = inviscid_flux(fake_dis, eos, q) logger.info(f"{dim}d flux = {flux}") # first two components should be nonzero in livedim only expected_flux = mom logger.info("Testing continuity") for i in range(dim): assert la.norm((flux[0, i] - expected_flux[i]).get()) == 0.0 if i != livedim: assert la.norm(flux[0, i].get()) == 0.0 else: assert la.norm(flux[0, i].get()) > 0.0 logger.info("Testing energy") expected_flux = mom * make_obj_array([(energy + p) / mass]) for i in range(dim): assert la.norm((flux[1, i] - expected_flux[i]).get()) == 0.0 if i != livedim: assert la.norm(flux[1, i].get()) == 0.0 else: assert la.norm(flux[1, i].get()) > 0.0 logger.info("Testing momentum") xpmomflux = make_obj_array([ (mom[i] * mom[j] / mass + (p if i == j else 0)) for i in range(dim) for j in range(dim) ]) for i in range(dim): expected_flux = xpmomflux[i * dim:(i + 1) * dim] for j in range(dim): assert la.norm( (flux[2 + i, j] - expected_flux[j]).get()) == 0 if i == j: if i == livedim: assert (la.norm(flux[2 + i, j].get()) > 0.0) else: # just for sanity - make sure the flux recovered the # prescribed value of p0 (within fp tol) for k in range(ntestnodes): assert np.abs(flux[2 + i, j][k] - p0) < tolerance else: assert la.norm(flux[2 + i, j].get()) == 0.0
def test_inviscid_flux(actx_factory, nspecies, dim): """Check inviscid flux against exact expected result: Identity test. Directly check inviscid flux routine, :func:`mirgecom.inviscid.inviscid_flux`, against the exact expected result. This test is designed to fail if the flux routine is broken. The expected inviscid flux is: F(q) = <rhoV, (E+p)V, rho(V.x.V) + pI, rhoY V> """ actx = actx_factory() nel_1d = 16 from meshmode.mesh.generation import generate_regular_rect_mesh # for dim in [1, 2, 3]: 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}") def rand(): from meshmode.dof_array import DOFArray return DOFArray( actx, tuple( actx.from_numpy(np.random.rand(grp.nelements, grp.nunit_dofs)) for grp in discr.discr_from_dd("vol").groups)) mass = rand() energy = rand() mom = make_obj_array([rand() for _ in range(dim)]) mass_fractions = make_obj_array([rand() for _ in range(nspecies)]) species_mass = mass * mass_fractions cv = make_conserved(dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass) # {{{ create the expected result p = eos.pressure(cv) escale = (energy + p) / mass numeq = dim + 2 + nspecies expected_flux = np.zeros((numeq, dim), dtype=object) expected_flux[0] = mom expected_flux[1] = mom * escale for i in range(dim): for j in range(dim): expected_flux[2 + i, j] = (mom[i] * mom[j] / mass + (p if i == j else 0)) for i in range(nspecies): expected_flux[dim + 2 + i] = mom * mass_fractions[i] expected_flux = make_conserved(dim, q=expected_flux) # }}} from mirgecom.gas_model import GasModel, make_fluid_state gas_model = GasModel(eos=eos) state = make_fluid_state(cv, gas_model) flux = inviscid_flux(state) flux_resid = flux - expected_flux for i in range(numeq, dim): for j in range(dim): assert (la.norm(flux_resid[i, j].get())) == 0.0
def test_poiseuille_fluxes(actx_factory, order, kappa): """Test the viscous fluxes using a Poiseuille input state.""" actx = actx_factory() dim = 2 from pytools.convergence import EOCRecorder e_eoc_rec = EOCRecorder() p_eoc_rec = EOCRecorder() base_pressure = 100000.0 pressure_ratio = 1.001 mu = 42 # arbitrary left_boundary_location = 0 right_boundary_location = 0.1 ybottom = 0. ytop = .02 nspecies = 0 spec_diffusivity = 0 * np.ones(nspecies) transport_model = SimpleTransport(viscosity=mu, thermal_conductivity=kappa, species_diffusivity=spec_diffusivity) xlen = right_boundary_location - left_boundary_location p_low = base_pressure p_hi = pressure_ratio * base_pressure dpdx = (p_low - p_hi) / xlen rho = 1.0 eos = IdealSingleGas() gas_model = GasModel(eos=eos, transport=transport_model) from mirgecom.initializers import PlanarPoiseuille initializer = PlanarPoiseuille(density=rho, mu=mu) def _elbnd_flux(discr, compute_interior_flux, compute_boundary_flux, int_tpair, boundaries): return (compute_interior_flux(int_tpair) + sum(compute_boundary_flux(btag) for btag in boundaries)) from mirgecom.flux import gradient_flux_central def cv_flux_interior(int_tpair): normal = thaw(actx, discr.normal(int_tpair.dd)) flux_weak = gradient_flux_central(int_tpair, normal) dd_all_faces = int_tpair.dd.with_dtag("all_faces") return discr.project(int_tpair.dd, dd_all_faces, flux_weak) def cv_flux_boundary(btag): boundary_discr = discr.discr_from_dd(btag) bnd_nodes = thaw(actx, boundary_discr.nodes()) cv_bnd = initializer(x_vec=bnd_nodes, eos=eos) bnd_nhat = thaw(actx, discr.normal(btag)) from grudge.trace_pair import TracePair bnd_tpair = TracePair(btag, interior=cv_bnd, exterior=cv_bnd) flux_weak = gradient_flux_central(bnd_tpair, bnd_nhat) dd_all_faces = bnd_tpair.dd.with_dtag("all_faces") return discr.project(bnd_tpair.dd, dd_all_faces, flux_weak) for nfac in [1, 2, 4]: npts_axis = nfac * (11, 21) box_ll = (left_boundary_location, ybottom) box_ur = (right_boundary_location, ytop) mesh = _get_box_mesh(2, a=box_ll, b=box_ur, n=npts_axis) logger.info(f"Number of {dim}d elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) def inf_norm(x): return actx.to_numpy(discr.norm(x, np.inf)) # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(discr) # form exact cv cv = initializer(x_vec=nodes, eos=eos) cv_int_tpair = interior_trace_pair(discr, cv) boundaries = [BTAG_ALL] cv_flux_bnd = _elbnd_flux(discr, cv_flux_interior, cv_flux_boundary, cv_int_tpair, boundaries) from mirgecom.operators import grad_operator from grudge.dof_desc import as_dofdesc dd_vol = as_dofdesc("vol") dd_faces = as_dofdesc("all_faces") grad_cv = grad_operator(discr, dd_vol, dd_faces, cv, cv_flux_bnd) xp_grad_cv = initializer.exact_grad(x_vec=nodes, eos=eos, cv_exact=cv) xp_grad_v = 1 / cv.mass * xp_grad_cv.momentum xp_tau = mu * (xp_grad_v + xp_grad_v.transpose()) # sanity check the gradient: relerr_scale_e = 1.0 / inf_norm(xp_grad_cv.energy) relerr_scale_p = 1.0 / inf_norm(xp_grad_cv.momentum) graderr_e = inf_norm(grad_cv.energy - xp_grad_cv.energy) graderr_p = inf_norm(grad_cv.momentum - xp_grad_cv.momentum) graderr_e *= relerr_scale_e graderr_p *= relerr_scale_p assert graderr_e < 5e-7 assert graderr_p < 5e-11 zeros = discr.zeros(actx) ones = zeros + 1 pressure = eos.pressure(cv) # grad of p should be dp/dx xp_grad_p = make_obj_array([dpdx * ones, zeros]) grad_p = op.local_grad(discr, pressure) dpscal = 1.0 / np.abs(dpdx) temperature = eos.temperature(cv) tscal = rho * eos.gas_const() * dpscal xp_grad_t = xp_grad_p / (cv.mass * eos.gas_const()) grad_t = op.local_grad(discr, temperature) # sanity check assert inf_norm(grad_p - xp_grad_p) * dpscal < 5e-9 assert inf_norm(grad_t - xp_grad_t) * tscal < 5e-9 fluid_state = make_fluid_state(cv, gas_model) # verify heat flux from mirgecom.viscous import conductive_heat_flux heat_flux = conductive_heat_flux(fluid_state, grad_t) xp_heat_flux = -kappa * xp_grad_t assert inf_norm(heat_flux - xp_heat_flux) < 2e-8 xp_e_flux = np.dot(xp_tau, cv.velocity) - xp_heat_flux xp_mom_flux = xp_tau from mirgecom.viscous import viscous_flux vflux = viscous_flux(fluid_state, grad_cv, grad_t) efluxerr = (inf_norm(vflux.energy - xp_e_flux) / inf_norm(xp_e_flux)) momfluxerr = (inf_norm(vflux.momentum - xp_mom_flux) / inf_norm(xp_mom_flux)) assert inf_norm(vflux.mass) == 0 e_eoc_rec.add_data_point(actx.to_numpy(h_max), efluxerr) p_eoc_rec.add_data_point(actx.to_numpy(h_max), momfluxerr) assert (e_eoc_rec.order_estimate() >= order - 0.5 or e_eoc_rec.max_error() < 3e-9) assert (p_eoc_rec.order_estimate() >= order - 0.5 or p_eoc_rec.max_error() < 2e-12)