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 = make_conserved(dim, q=local_grad(discr, cv.join())) from mirgecom.fluid import species_mass_fraction_gradient grad_y = species_mass_fraction_gradient(discr, cv, grad_cv) assert grad_y.shape == (nspecies, dim) from meshmode.dof_array import DOFArray assert type(grad_y[0, 0]) == DOFArray 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 discr.norm(grad_y[ispec] - exact_grad, np.inf) < tol assert discr.norm(grad_y[ispec + 1] + exact_grad, np.inf) < tol
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 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) 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 = make_conserved(dim, q=op.local_grad(discr, cv.join())) if transport_model: tv_model = SimpleTransport(bulk_viscosity=1.0, viscosity=0.5) else: tv_model = PowerLawTransport() eos = IdealSingleGas(transport_model=tv_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(discr, eos, cv, grad_cv) # The errors come from grad_v assert discr.norm(tau - exp_tau, np.inf) < 1e-12
def euler_operator(discr, eos, boundaries, cv, time=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 time 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. """ inviscid_flux_vol = inviscid_flux(discr, eos, cv) inviscid_flux_bnd = (inviscid_facial_flux( discr, eos=eos, cv_tpair=interior_trace_pair(discr, cv)) + sum( inviscid_facial_flux( discr, eos=eos, cv_tpair=TracePair( part_tpair.dd, interior=make_conserved(discr.dim, q=part_tpair.int), exterior=make_conserved(discr.dim, q=part_tpair.ext))) for part_tpair in cross_rank_trace_pairs(discr, cv.join())) + sum(boundaries[btag].inviscid_boundary_flux( discr, btag=btag, cv=cv, eos=eos, time=time) for btag in boundaries)) q = -div_operator(discr, inviscid_flux_vol.join(), inviscid_flux_bnd.join()) return make_conserved(discr.dim, q=q)
def __call__(self, x_vec, eos, **kwargs): """ Create the mixture state at locations *x_vec* (t is ignored). Parameters ---------- x_vec: numpy.ndarray Coordinates at which solution is desired eos: Mixture-compatible equation-of-state object must provide these functions: `eos.get_density` `eos.get_internal_energy` """ if x_vec.shape != (self._dim,): raise ValueError(f"Position vector has unexpected dimensionality," f" expected {self._dim}.") ones = (1.0 + x_vec[0]) - x_vec[0] pressure = self._pressure * ones temperature = self._temperature * ones velocity = make_obj_array([self._velocity[i] * ones for i in range(self._dim)]) y = make_obj_array([self._massfracs[i] * ones for i in range(self._nspecies)]) mass = eos.get_density(pressure, temperature, y) specmass = mass * y mom = mass * velocity internal_energy = eos.get_internal_energy(temperature=temperature, species_mass_fractions=y) kinetic_energy = 0.5 * np.dot(velocity, velocity) energy = mass * (internal_energy + kinetic_energy) return make_conserved(dim=self._dim, mass=mass, energy=energy, momentum=mom, species_mass=specmass)
def viscous_flux(discr, eos, cv, grad_cv, grad_t): r"""Compute the viscous flux vectors. The viscous fluxes are: .. math:: \mathbf{F}_V = [0,\tau\cdot\mathbf{v} - \mathbf{q}, \tau,-\mathbf{J}_\alpha], with fluid velocity ($\mathbf{v}$), viscous stress tensor ($\mathbf{\tau}$), heat flux ($\mathbf{q}$), and diffusive flux for each species ($\mathbf{J}_\alpha$). .. note:: The fluxes are returned as a :class:`mirgecom.fluid.ConservedVars` object with a *dim-vector* for each conservation equation. See :class:`mirgecom.fluid.ConservedVars` for more information about how the fluxes are represented. Parameters ---------- discr: :class:`grudge.eager.EagerDGDiscretization` The discretization to use eos: :class:`~mirgecom.eos.GasEOS` A gas equation of state cv: :class:`~mirgecom.fluid.ConservedVars` Fluid state grad_cv: :class:`~mirgecom.fluid.ConservedVars` Gradient of the fluid state grad_t: numpy.ndarray Gradient of the fluid temperature Returns ------- :class:`~mirgecom.fluid.ConservedVars` or float The viscous transport flux vector if viscous transport properties are provided, scalar zero otherwise. """ transport = eos.transport_model() if transport is None: return 0 dim = cv.dim viscous_mass_flux = 0 * cv.momentum j = diffusive_flux(discr, eos, cv, grad_cv) heat_flux_diffusive = diffusive_heat_flux(discr, eos, cv, j) tau = viscous_stress_tensor(discr, eos, cv, grad_cv) viscous_energy_flux = (np.dot(tau, cv.velocity) - conductive_heat_flux(discr, eos, cv, grad_t) - heat_flux_diffusive) return make_conserved(dim, mass=viscous_mass_flux, energy=viscous_energy_flux, momentum=tau, species_mass=-j)
def adiabatic_slip_pair(self, discr, cv, btag, **kwargs): """Get the interior and exterior solution on the boundary. The exterior solution is set such that there will be vanishing flux through the boundary, preserving mass, momentum (magnitude) and energy. rho_plus = rho_minus v_plus = v_minus - 2 * (v_minus . n_hat) * n_hat mom_plus = rho_plus * v_plus E_plus = E_minus """ # Grab some boundary-relevant data dim = discr.dim actx = cv.mass.array_context # Grab a unit normal to the boundary nhat = thaw(actx, discr.normal(btag)) # Get the interior/exterior solns int_cv = discr.project("vol", btag, cv) # Subtract out the 2*wall-normal component # of velocity from the velocity at the wall to # induce an equal but opposite wall-normal (reflected) wave # preserving the tangential component mom_normcomp = np.dot(int_cv.momentum, nhat) # wall-normal component wnorm_mom = nhat * mom_normcomp # wall-normal mom vec ext_mom = int_cv.momentum - 2.0 * wnorm_mom # prescribed ext momentum # Form the external boundary solution with the new momentum ext_cv = make_conserved(dim=dim, mass=int_cv.mass, energy=int_cv.energy, momentum=ext_mom, species_mass=int_cv.species_mass) return TracePair(btag, interior=int_cv, exterior=ext_cv)
def get_species_source_terms(self, cv: ConservedVars, temperature): r"""Get the species mass source terms to be used on the RHS for chemistry. Parameters ---------- cv: :class:`~mirgecom.fluid.ConservedVars` :class:`~mirgecom.fluid.ConservedVars` containing at least the mass ($\rho$), energy ($\rho{E}$), momentum ($\rho\vec{V}$), and the vector of species masses, ($\rho{Y}_\alpha$). Returns ------- :class:`~mirgecom.fluid.ConservedVars` Chemistry source terms """ omega = self.get_production_rates(cv, temperature) w = self.get_species_molecular_weights() dim = cv.dim species_sources = w * omega rho_source = 0 * cv.mass mom_source = 0 * cv.momentum energy_source = 0 * cv.energy return make_conserved(dim, rho_source, energy_source, mom_source, species_sources)
def viscous_flux(state, grad_cv, grad_t): r"""Compute the viscous flux vectors. The viscous fluxes are: .. math:: \mathbf{F}_V = [0,\tau\cdot\mathbf{v} - \mathbf{q}, \tau,-\mathbf{J}_\alpha], with fluid velocity ($\mathbf{v}$), viscous stress tensor ($\mathbf{\tau}$), heat flux ($\mathbf{q}$), and diffusive flux for each species ($\mathbf{J}_\alpha$). .. note:: The fluxes are returned as a :class:`mirgecom.fluid.ConservedVars` object with a *dim-vector* for each conservation equation. See :class:`mirgecom.fluid.ConservedVars` for more information about how the fluxes are represented. Parameters ---------- state: :class:`~mirgecom.gas_model.FluidState` Full fluid conserved and thermal state grad_cv: :class:`~mirgecom.fluid.ConservedVars` Gradient of the fluid state grad_t: numpy.ndarray Gradient of the fluid temperature Returns ------- :class:`~mirgecom.fluid.ConservedVars` or float The viscous transport flux vector if viscous transport properties are provided, scalar zero otherwise. """ if not state.is_viscous: import warnings warnings.warn("Viscous fluxes requested for inviscid state.") return 0 viscous_mass_flux = 0 * state.momentum_density tau = viscous_stress_tensor(state, grad_cv) j = diffusive_flux(state, grad_cv) viscous_energy_flux = (np.dot(tau, state.velocity) - diffusive_heat_flux(state, j) - conductive_heat_flux(state, grad_t)) return make_conserved(state.dim, mass=viscous_mass_flux, energy=viscous_energy_flux, momentum=tau, species_mass=-j)
def __call__(self, x_vec, *, t=0, eos=None): """ Create a uniform flow solution at locations *x_vec*. Parameters ---------- t: float Current time at which the solution is desired (unused) x_vec: numpy.ndarray Nodal coordinates eos: :class:`mirgecom.eos.IdealSingleGas` Equation of state class with method to supply gas *gamma*. """ if eos is None: eos = IdealSingleGas() gamma = eos.gamma() mass = 0.0 * x_vec[0] + self._rho mom = self._velocity * mass energy = (self._p / (gamma - 1.0)) + np.dot(mom, mom) / (2.0 * mass) species_mass = self._mass_fracs * mass return make_conserved(dim=self._dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass)
def exact_rhs(self, discr, cv, t=0.0): """ Create the RHS for the uniform solution. (Hint - it should be all zero). Parameters ---------- q State array which expects at least the canonical conserved quantities (mass, energy, momentum) for the fluid at each point. (unused) t: float Time at which RHS is desired (unused) """ actx = cv.array_context nodes = thaw(actx, discr.nodes()) mass = nodes[0].copy() mass[:] = 1.0 massrhs = 0.0 * mass energyrhs = 0.0 * mass momrhs = make_obj_array([0 * mass for i in range(self._dim)]) yrhs = make_obj_array([0 * mass for i in range(self._nspecies)]) return make_conserved(dim=self._dim, mass=massrhs, energy=energyrhs, momentum=momrhs, species_mass=yrhs)
def inviscid_flux(discr, eos, cv): r"""Compute the inviscid flux vectors from fluid conserved vars *cv*. The inviscid fluxes are $(\rho\vec{V},(\rho{E}+p)\vec{V},\rho(\vec{V}\otimes\vec{V}) +p\mathbf{I}, \rho{Y_s}\vec{V})$ .. note:: The fluxes are returned as a :class:`mirgecom.fluid.ConservedVars` object with a *dim-vector* for each conservation equation. See :class:`mirgecom.fluid.ConservedVars` for more information about how the fluxes are represented. """ dim = cv.dim p = eos.pressure(cv) mom = cv.momentum return make_conserved( dim, mass=mom, energy=mom * (cv.energy + p) / cv.mass, momentum=np.outer(mom, mom) / cv.mass + np.eye(dim) * p, species_mass=( # reshaped: (nspecies, dim) (mom / cv.mass) * cv.species_mass.reshape(-1, 1)))
def sym_grad(dim, expr): if isinstance(expr, ConservedVars): return make_conserved(dim, q=sym_grad(dim, expr.join())) elif isinstance(expr, np.ndarray): return np.stack(obj_array_vectorize(lambda e: sym.grad(dim, e), expr)) else: return sym.grad(dim, expr)
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 = make_conserved(dim, q=local_grad(discr, cv.join())) grad_v = velocity_gradient(discr, 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 assert discr.norm(grad_v - exp_result, np.inf) < tol assert discr.norm(grad_v.T - exp_trans, np.inf) < tol assert discr.norm(np.trace(grad_v) - exp_trace, np.inf) < tol
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_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 inviscid_operator(discr, eos, boundaries, q, t=0.0): """Interface :function:`euler_operator` with backwards-compatible API.""" from warnings import warn warn( "Do not call inviscid_operator; it is now called euler_operator. This" "function will disappear August 1, 2021", DeprecationWarning, stacklevel=2) return euler_operator(discr, eos, boundaries, make_conserved(discr.dim, q=q), t)
def flux_lfr(cv_tpair, f_tpair, normal, lam): r"""Compute Lax-Friedrichs/Rusanov flux after [Hesthaven_2008]_, Section 6.6. The Lax-Friedrichs/Rusanov flux is calculated as: .. math:: f_{\mathtt{LFR}} = \frac{1}{2}(\mathbf{F}(q^-) + \mathbf{F}(q^+)) + \frac{\lambda}{2}(q^{-} - q^{+})\hat{\mathbf{n}}, where $q^-, q^+$ are the scalar solution components on the interior and the exterior of the face on which the LFR flux is to be calculated, $\mathbf{F}$ is the vector flux function, $\hat{\mathbf{n}}$ is the face normal, and $\lambda$ is the user-supplied jump term coefficient. The $\lambda$ parameter is system-specific. Specifically, for the Rusanov flux it is the max eigenvalue of the flux jacobian: .. math:: \lambda = \text{max}\left(|\mathbb{J}_{F}(q^-)|,|\mathbb{J}_{F}(q^+)|\right) Here, $\lambda$ is a function parameter, leaving the responsibility for the calculation of the eigenvalues of the system-dependent flux Jacobian to the caller. Parameters ---------- cv_tpair: :class:`~grudge.trace_pair.TracePair` Solution trace pair for faces for which numerical flux is to be calculated f_tpair: :class:`~grudge.trace_pair.TracePair` Physical flux trace pair on faces on which numerical flux is to be calculated normal: numpy.ndarray object array of :class:`~meshmode.dof_array.DOFArray` with outward-pointing normals lam: :class:`~meshmode.dof_array.DOFArray` lambda parameter for Lax-Friedrichs/Rusanov flux Returns ------- numpy.ndarray object array of :class:`~meshmode.dof_array.DOFArray` with the Lax-Friedrichs/Rusanov flux. """ return make_conserved(dim=len(normal), q=f_tpair.avg.join() - lam * np.outer(cv_tpair.diff.join(), normal) / 2)
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_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 get_species_source_terms(self, cv: ConservedVars): """Get the species mass source terms to be used on the RHS for chemistry.""" omega = self.get_production_rates(cv) w = self.get_species_molecular_weights() dim = len(cv.momentum) species_sources = w * omega rho_source = 0 * cv.mass mom_source = 0 * cv.momentum energy_source = 0 * cv.energy return make_conserved(dim, rho_source, energy_source, mom_source, species_sources)
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 gradient_flux_central(u_tpair, normal): r"""Compute a central flux for the gradient operator. The central gradient flux, $\mathbf{h}$, of a scalar quantity $u$ is calculated as: .. math:: \mathbf{h}({u}^-, {u}^+; \mathbf{n}) = \frac{1}{2} \left({u}^{+}+{u}^{-}\right)\mathbf{\hat{n}} where ${u}^-, {u}^+$, are the scalar function values on the interior and exterior of the face on which the central flux is to be calculated, and $\mathbf{\hat{n}}$ is the *normal* vector. *u_tpair* is the :class:`~grudge.trace_pair.TracePair` representing the scalar quantities ${u}^-, {u}^+$. *u_tpair* may also represent a vector-quantity :class:`~grudge.trace_pair.TracePair`, and in this case the central scalar flux is computed on each component of the vector quantity as an independent scalar. Parameters ---------- u_tpair: :class:`~grudge.trace_pair.TracePair` Trace pair for the face upon which flux calculation is to be performed normal: numpy.ndarray object array of :class:`~meshmode.dof_array.DOFArray` with outward-pointing normals Returns ------- numpy.ndarray object array of :class:`~meshmode.dof_array.DOFArray` with the flux for each scalar component. """ tp_avg = u_tpair.avg tp_join = tp_avg # FIXME: There's a better way in-the-works through an improved "outer". # Update when https://github.com/inducer/arraycontext/pull/46 lands. if isinstance(tp_avg, DOFArray): return tp_avg * normal elif isinstance(tp_avg, ConservedVars): tp_join = tp_avg.join() result = np.outer(tp_join, normal) if isinstance(tp_avg, ConservedVars): return make_conserved(tp_avg.dim, q=result) else: return result
def dummy(nodes, eos, cv=None, **kwargs): dim = len(nodes) if cv is not None: #cv = split_conserved(dim, q) mass = cv.mass momentum = cv.momentum ke = .5 * np.dot(cv.momentum, cv.momentum) / cv.mass energy = cv.energy species_mass = cv.species_mass return make_conserved(dim=dim, mass=mass, momentum=momentum, energy=energy, species_mass=species_mass)
def __call__(self, x_vec, *, t=0, eos=None): """ Create a multi-component lump solution at time *t* and locations *x_vec*. The solution at time *t* is created by advecting the component mass lump at the user-specified constant, uniform velocity (``MulticomponentLump._velocity``). Parameters ---------- t: float Current time at which the solution is desired x_vec: numpy.ndarray Nodal coordinates eos: :class:`mirgecom.eos.IdealSingleGas` Equation of state class with method to supply gas *gamma*. """ if eos is None: eos = IdealSingleGas() if x_vec.shape != (self._dim, ): print(f"len(x_vec) = {len(x_vec)}") print(f"self._dim = {self._dim}") raise ValueError(f"Expected {self._dim}-dimensional inputs.") actx = x_vec[0].array_context loc_update = t * self._velocity gamma = eos.gamma() mass = 0 * x_vec[0] + self._rho0 mom = self._velocity * mass energy = (self._p0 / (gamma - 1.0)) + np.dot(mom, mom) / (2.0 * mass) # process the species components independently species_mass = np.empty((self._nspecies, ), dtype=object) for i in range(self._nspecies): lump_loc = self._spec_centers[i] + loc_update rel_pos = x_vec - lump_loc r2 = np.dot(rel_pos, rel_pos) expterm = self._spec_amplitudes[i] * actx.np.exp(-r2) species_mass[i] = self._rho0 * (self._spec_y0s[i] + expterm) return make_conserved(dim=self._dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass)
def _cv_test_func(dim): """Make a CV array container for testing. There is no need for this CV to be physical, we are just testing whether the operator machinery still gets the right answers when operating on a CV array container. mass = constant energy = _trig_test_func momentum = <(dim_index+1)*_coord_test_func(order=2)> """ sym_mass = 1 sym_energy = _trig_test_func(dim) sym_momentum = make_obj_array([(i + 1) * _coord_test_func(dim, order=2) for i in range(dim)]) return make_conserved(dim=dim, mass=sym_mass, energy=sym_energy, momentum=sym_momentum)
def test_restart_cv(actx_factory, nspecies): """Test that restart can read a CV array container.""" actx = actx_factory() nel_1d = 4 dim = 3 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) from meshmode.dof_array import thaw nodes = thaw(actx, discr.nodes()) mass = nodes[0] energy = nodes[1] mom = make_obj_array([nodes[2] * (i + 3) for i in range(dim)]) species_mass = None if nspecies > 0: mass_fractions = make_obj_array( [i * nodes[0] for i in range(nspecies)]) species_mass = mass * mass_fractions rst_filename = f"test_{nspecies}.pkl" from mirgecom.fluid import make_conserved test_state = make_conserved(dim, mass=mass, energy=energy, momentum=mom, species_mass=species_mass) rst_data = {"state": test_state} from mirgecom.restart import write_restart_file write_restart_file(actx, rst_data, rst_filename) from mirgecom.restart import read_restart_data restart_data = read_restart_data(actx, rst_filename) resid = test_state - restart_data["state"] assert discr.norm(resid.join(), np.inf) == 0
def __call__(self, x_vec, *, t=0, eos=None): """ Create the isentropic vortex solution at time *t* at locations *x_vec*. The solution at time *t* is created by advecting the vortex under the assumption of user-supplied constant, uniform velocity (``Vortex2D._velocity``). Parameters ---------- t: float Current time at which the solution is desired. x_vec: numpy.ndarray Nodal coordinates eos: mirgecom.eos.IdealSingleGas Equation of state class to supply method for gas *gamma*. """ if eos is None: eos = IdealSingleGas() vortex_loc = self._center + t * self._velocity # coordinates relative to vortex center x_rel = x_vec[0] - vortex_loc[0] y_rel = x_vec[1] - vortex_loc[1] actx = x_vec[0].array_context gamma = eos.gamma() r = actx.np.sqrt(x_rel**2 + y_rel**2) expterm = self._beta * actx.np.exp(1 - r**2) u = self._velocity[0] - expterm * y_rel / (2 * np.pi) v = self._velocity[1] + expterm * x_rel / (2 * np.pi) velocity = make_obj_array([u, v]) mass = (1 - (gamma - 1) / (16 * gamma * np.pi**2) * expterm**2)**(1 / (gamma - 1)) momentum = mass * velocity p = mass**gamma energy = p / (gamma - 1) + mass / 2 * (u**2 + v**2) return make_conserved(dim=2, mass=mass, energy=energy, momentum=momentum)
def exact_rhs(self, discr, cv, t=0.0): """ Create a RHS for multi-component lump soln at time *t*, locations *x_vec*. The RHS at time *t* is created by advecting the species mass lump at the user-specified constant, uniform velocity (``MulticomponentLump._velocity``). Parameters ---------- q State array which expects at least the canonical conserved quantities (mass, energy, momentum) for the fluid at each point. t: float Time at which RHS is desired """ actx = cv.array_context nodes = thaw(actx, discr.nodes()) loc_update = t * self._velocity mass = 0 * nodes[0] + self._rho0 mom = self._velocity * mass v = mom / mass massrhs = 0 * mass energyrhs = 0 * mass momrhs = 0 * mom # process the species components independently specrhs = np.empty((self._nspecies, ), dtype=object) for i in range(self._nspecies): lump_loc = self._spec_centers[i] + loc_update rel_pos = nodes - lump_loc r2 = np.dot(rel_pos, rel_pos) expterm = self._spec_amplitudes[i] * actx.np.exp(-r2) specrhs[i] = 2 * self._rho0 * expterm * np.dot(rel_pos, v) return make_conserved(dim=self._dim, mass=massrhs, energy=energyrhs, momentum=momrhs, species_mass=specrhs)
def exact_rhs(self, discr, cv, t=0.0): """ Create the RHS for the lump-of-mass solution at time *t*, locations *x_vec*. The RHS at time *t* is created by advecting the mass lump under the assumption of constant, uniform velocity (``Lump._velocity``). Parameters ---------- q State array which expects at least the canonical conserved quantities (mass, energy, momentum) for the fluid at each point. t: float Time at which RHS is desired """ actx = cv.array_context nodes = thaw(actx, discr.nodes()) lump_loc = self._center + t * self._velocity # coordinates relative to lump center rel_center = make_obj_array( [nodes[i] - lump_loc[i] for i in range(self._dim)]) r = actx.np.sqrt(np.dot(rel_center, rel_center)) # The expected rhs is: # rhorhs = -2*rho*(r.dot.v) # rhoerhs = -rho*v^2*(r.dot.v) # rhovrhs = -2*rho*(r.dot.v)*v expterm = self._rhoamp * actx.np.exp(1 - r**2) mass = expterm + self._rho0 v = self._velocity / mass v2 = np.dot(v, v) rdotv = np.dot(rel_center, v) massrhs = -2 * rdotv * mass energyrhs = -v2 * rdotv * mass momrhs = v * (-2 * mass * rdotv) return make_conserved(dim=self._dim, mass=massrhs, energy=energyrhs, momentum=momrhs)