def euler_operator(discr, eos, boundaries, cv, t=0.0): r"""Compute RHS of the Euler flow equations. Returns ------- numpy.ndarray The right-hand-side of the Euler flow equations: .. math:: \dot{\mathbf{q}} = - \nabla\cdot\mathbf{F} + (\mathbf{F}\cdot\hat{n})_{\partial\Omega} Parameters ---------- cv: :class:`mirgecom.fluid.ConservedVars` Fluid conserved state object with the conserved variables. boundaries Dictionary of boundary functions, one for each valid btag t Time eos: mirgecom.eos.GasEOS Implementing the pressure and temperature functions for returning pressure and temperature as a function of the state q. Returns ------- numpy.ndarray Agglomerated object array of DOF arrays representing the RHS of the Euler flow equations. """ vol_weak = discr.weak_div(inviscid_flux(discr=discr, eos=eos, cv=cv).join()) boundary_flux = ( _facial_flux(discr=discr, eos=eos, cv_tpair=interior_trace_pair(discr, cv)) + sum( _facial_flux( discr, eos=eos, cv_tpair=TracePair( part_pair.dd, interior=split_conserved(discr.dim, part_pair.int), exterior=split_conserved(discr.dim, part_pair.ext))) for part_pair in cross_rank_trace_pairs(discr, cv.join())) + sum( _facial_flux( discr=discr, eos=eos, cv_tpair=boundaries[btag].boundary_pair( discr, eos=eos, btag=btag, t=t, cv=cv) ) for btag in boundaries) ).join() return split_conserved( discr.dim, discr.inverse_mass(vol_weak - discr.face_mass(boundary_flux)) )
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_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 euler_operator(discr, state, gas_model, boundaries, time=0.0, quadrature_tag=None): r"""Compute RHS of the Euler flow equations. Returns ------- numpy.ndarray The right-hand-side of the Euler flow equations: .. math:: \dot{\mathbf{q}} = - \nabla\cdot\mathbf{F} + (\mathbf{F}\cdot\hat{n})_{\partial\Omega} Parameters ---------- state: :class:`~mirgecom.gas_model.FluidState` Fluid state object with the conserved state, and dependent quantities. boundaries Dictionary of boundary functions, one for each valid btag time Time gas_model: :class:`~mirgecom.gas_model.GasModel` Physical gas model including equation of state, transport, and kinetic properties as required by fluid state quadrature_tag An optional identifier denoting a particular quadrature discretization to use during operator evaluations. The default value is *None*. Returns ------- :class:`mirgecom.fluid.ConservedVars` """ dd_base_vol = DOFDesc("vol") dd_quad_vol = DOFDesc("vol", quadrature_tag) dd_quad_faces = DOFDesc("all_faces", quadrature_tag) # project pair to the quadrature discretization and update dd to quad def _interp_to_surf_quad(utpair): local_dd = utpair.dd local_dd_quad = local_dd.with_discr_tag(quadrature_tag) return TracePair(local_dd_quad, interior=op.project(discr, local_dd, local_dd_quad, utpair.int), exterior=op.project(discr, local_dd, local_dd_quad, utpair.ext)) boundary_states_quad = { btag: project_fluid_state(discr, dd_base_vol, as_dofdesc(btag).with_discr_tag(quadrature_tag), state, gas_model) for btag in boundaries } # performs MPI communication of CV if needed cv_interior_pairs = [ # Get the interior trace pairs onto the surface quadrature # discretization (if any) _interp_to_surf_quad(tpair) for tpair in interior_trace_pairs(discr, state.cv) ] tseed_interior_pairs = None if state.is_mixture: # If this is a mixture, we need to exchange the temperature field because # mixture pressure (used in the inviscid flux calculations) depends on # temperature and we need to seed the temperature calculation for the # (+) part of the partition boundary with the remote temperature data. tseed_interior_pairs = [ # Get the interior trace pairs onto the surface quadrature # discretization (if any) _interp_to_surf_quad(tpair) for tpair in interior_trace_pairs(discr, state.temperature) ] interior_states_quad = make_fluid_state_trace_pairs( cv_interior_pairs, gas_model, tseed_interior_pairs) # Interpolate the fluid state to the volume quadrature grid # (this includes the conserved and dependent quantities) vol_state_quad = project_fluid_state(discr, dd_base_vol, dd_quad_vol, state, gas_model) # Compute volume contributions inviscid_flux_vol = inviscid_flux(vol_state_quad) # Compute interface contributions inviscid_flux_bnd = ( # Interior faces sum( inviscid_facial_flux(discr, state_pair) for state_pair in interior_states_quad) # Domain boundary faces + sum(boundaries[btag].inviscid_divergence_flux( discr, as_dofdesc(btag).with_discr_tag(quadrature_tag), gas_model, state_minus=boundary_states_quad[btag], time=time) for btag in boundaries)) return -div_operator(discr, dd_quad_vol, dd_quad_faces, inviscid_flux_vol, inviscid_flux_bnd)