def get_diffusion_flux(self, discr, quad_tag, dd, alpha, grad_u): # noqa: D102 alpha_int = discr.project("vol", dd, alpha) alpha_tpair = TracePair(dd, interior=alpha_int, exterior=alpha_int) grad_u_int = discr.project("vol", dd, grad_u) grad_u_tpair = TracePair(dd, interior=grad_u_int, exterior=grad_u_int) return diffusion_flux(discr, quad_tag, alpha_tpair, grad_u_tpair)
def diffusion_flux(discr, quad_tag, alpha_tpair, grad_u_tpair): r"""Compute the numerical flux for $\nabla \cdot (\alpha \nabla u)$.""" actx = grad_u_tpair.int[0].array_context dd = grad_u_tpair.dd dd_quad = dd.with_discr_tag(quad_tag) dd_allfaces_quad = dd_quad.with_dtag("all_faces") normal_quad = thaw(actx, discr.normal(dd_quad)) def to_quad(a): return discr.project(dd, dd_quad, a) def flux(alpha, grad_u, normal): return -alpha * np.dot(grad_u, normal) flux_tpair = TracePair(dd_quad, interior=flux(to_quad(alpha_tpair.int), to_quad(grad_u_tpair.int), normal_quad), exterior=flux(to_quad(alpha_tpair.ext), to_quad(grad_u_tpair.ext), normal_quad)) return discr.project(dd_quad, dd_allfaces_quad, flux_tpair.avg)
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 test_trace_pair(actx_factory): """Simple smoke test for :class:`grudge.trace_pair.TracePair`.""" actx = actx_factory() dim = 3 order = 1 n = 4 mesh = mgen.generate_regular_rect_mesh( a=(-1,)*dim, b=(1,)*dim, nelements_per_axis=(n,)*dim) dcoll = DiscretizationCollection(actx, mesh, order=order) def rand(): return DOFArray( actx, tuple(actx.from_numpy( np.random.rand(grp.nelements, grp.nunit_dofs)) for grp in dcoll.discr_from_dd("vol").groups)) interior = rand() exterior = rand() tpair = TracePair("vol", interior=interior, exterior=exterior) import grudge.op as op assert op.norm(dcoll, tpair.avg - 0.5*(exterior + interior), np.inf) == 0 assert op.norm(dcoll, tpair.diff - (exterior - interior), np.inf) == 0 assert op.norm(dcoll, tpair.int - interior, np.inf) == 0 assert op.norm(dcoll, tpair.ext - exterior, np.inf) == 0
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))
def viscous_facial_flux(discr, state_tpair, grad_cv_tpair, grad_t_tpair, local=False): """Return the viscous flux across a face given the solution on both sides. Parameters ---------- discr: :class:`~grudge.eager.EagerDGDiscretization` The discretization to use state_tpair: :class:`~grudge.trace_pair.TracePair` Trace pair of :class:`~mirgecom.gas_model.FluidState` with the full fluid conserved and thermal state on the faces grad_cv_tpair: :class:`~grudge.trace_pair.TracePair` Trace pair of :class:`~mirgecom.fluid.ConservedVars` with the gradient of the fluid solution on the faces grad_t_tpair: :class:`~grudge.trace_pair.TracePair` Trace pair of temperature gradient on the faces. local: bool Indicates whether to skip projection of fluxes to "all_faces" or not. If set to *False* (the default), the returned fluxes are projected to "all_faces". If set to *True*, the returned fluxes are not projected to "all_faces"; remaining instead on the boundary restriction. Returns ------- :class:`~mirgecom.fluid.ConservedVars` The viscous transport flux in the face-normal direction on "all_faces" or local to the sub-discretization depending on *local* input parameter """ actx = state_tpair.int.array_context dd = state_tpair.dd dd_all_faces = dd.with_dtag("all_faces") normal = thaw(actx, discr.normal(dd)) f_int = viscous_flux(state_tpair.int, grad_cv_tpair.int, grad_t_tpair.int) f_ext = viscous_flux(state_tpair.ext, grad_cv_tpair.ext, grad_t_tpair.ext) f_tpair = TracePair(dd, interior=f_int, exterior=f_ext) # todo: user-supplied flux routine # note: Hard-code central flux here for BR1 flux_weak = divergence_flux_central(f_tpair, normal) if not local: return discr.project(dd, dd_all_faces, flux_weak) return flux_weak
def boundary_pair(self, discr, cv, btag, **kwargs): """Get the interior and exterior solution on the boundary.""" actx = cv.array_context boundary_discr = discr.discr_from_dd(btag) nodes = thaw(actx, boundary_discr.nodes()) ext_soln = self._userfunc(nodes, **kwargs) int_soln = discr.project("vol", btag, cv) return TracePair(btag, interior=int_soln, exterior=ext_soln)
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) return discr.project(bnd_tpair.dd, "all_faces", flux_weak)
def central_flux_boundary(actx, discr, soln_func, btag): """Compute a central flux for boundary faces.""" boundary_discr = discr.discr_from_dd(btag) bnd_nodes = thaw(actx, boundary_discr.nodes()) soln_bnd = soln_func(x_vec=bnd_nodes) bnd_nhat = thaw(actx, discr.normal(btag)) from grudge.trace_pair import TracePair bnd_tpair = TracePair(btag, interior=soln_bnd, exterior=soln_bnd) flux_weak = gradient_flux_central(bnd_tpair, bnd_nhat) return discr.project(bnd_tpair.dd, "all_faces", flux_weak)
def bdry_tpair(dd, interior, exterior): """ :arg interior: an expression that already lives on the boundary representing the interior value to be used for the flux. :arg exterior: an expression that already lives on the boundary representing the exterior value to be used for the flux. """ return TracePair(dd, interior=interior, exterior=exterior)
def inviscid_facial_flux(discr, eos, cv_tpair, local=False): r"""Return the flux across a face given the solution on both sides *q_tpair*. This flux is currently hard-coded to use a Rusanov-type local Lax-Friedrichs (LFR) numerical flux at element boundaries. The numerical inviscid flux $F^*$ is calculated as: .. math:: \mathbf{F}^{*}_{\mathtt{LFR}} = \frac{1}{2}(\mathbf{F}(q^-) +\mathbf{F}(q^+)) \cdot \hat{n} + \frac{\lambda}{2}(q^{-} - q^{+}), where $q^-, q^+$ are the fluid solution state on the interior and the exterior of the face on which the LFR flux is to be calculated, $\mathbf{F}$ is the inviscid fluid flux, $\hat{n}$ is the face normal, and $\lambda$ is the *local* maximum fluid wavespeed. Parameters ---------- eos: mirgecom.eos.GasEOS Implementing the pressure and temperature functions for returning pressure and temperature as a function of the state q. q_tpair: :class:`grudge.trace_pair.TracePair` Trace pair for the face upon which flux calculation is to be performed local: bool Indicates whether to skip projection of fluxes to "all_faces" or not. If set to *False* (the default), the returned fluxes are projected to "all_faces." If set to *True*, the returned fluxes are not projected to "all_faces"; remaining instead on the boundary restriction. """ actx = cv_tpair.int.array_context flux_tpair = TracePair(cv_tpair.dd, interior=inviscid_flux(discr, eos, cv_tpair.int), exterior=inviscid_flux(discr, eos, cv_tpair.ext)) # This calculates the local maximum eigenvalue of the flux Jacobian # for a single component gas, i.e. the element-local max wavespeed |v| + c. lam = actx.np.maximum(compute_wavespeed(eos=eos, cv=cv_tpair.int), compute_wavespeed(eos=eos, cv=cv_tpair.ext)) normal = thaw(actx, discr.normal(cv_tpair.dd)) # todo: user-supplied flux routine flux_weak = divergence_flux_lfr(cv_tpair, flux_tpair, normal=normal, lam=lam) if local is False: return discr.project(cv_tpair.dd, "all_faces", flux_weak) return flux_weak
def bv_tpair(dd, interior, exterior): """ :arg interior: an expression that lives in the volume and will be restricted to the boundary identified by *tag* before use. :arg exterior: an expression that already lives on the boundary representing the exterior value to be used for the flux. """ from grudge.symbolic.operators import project interior = cse(project("vol", dd)(interior)) return TracePair(dd, interior=interior, exterior=exterior)
def boundary_pair(self, discr, btag, cv, **kwargs): """Get the interior and exterior solution on the boundary.""" if self._bnd_pair_func: return self._bnd_pair_func(discr, cv=cv, btag=btag, **kwargs) if not self._fluid_soln_func: raise NotImplementedError() actx = cv.array_context boundary_discr = discr.discr_from_dd(btag) nodes = thaw(actx, boundary_discr.nodes()) nhat = thaw(actx, discr.normal(btag)) int_soln = discr.project("vol", btag, cv) ext_soln = self._fluid_soln_func(nodes, cv=int_soln, normal=nhat, **kwargs) return TracePair(btag, interior=int_soln, exterior=ext_soln)
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 make_fluid_state_trace_pairs(cv_pairs, gas_model, temperature_seed_pairs=None): """Create a fluid state from the conserved vars and equation of state. This routine helps create a thermally consistent fluid state out of a collection of CV (:class:`~mirgecom.fluid.ConservedVars`) pairs. It is useful for creating consistent boundary states for partition boundaries. Parameters ---------- cv_pairs: list of :class:`~grudge.trace_pair.TracePair` List of tracepairs of fluid CV (:class:`~mirgecom.fluid.ConservedVars`) for each boundary on which the thermally consistent state is desired gas_model: :class:`~mirgecom.gas_model.GasModel` The physical model constructs for the gas_model temperature_seed_pairs: list of :class:`~grudge.trace_pair.TracePair` List of tracepairs of :class:`~meshmode.dof_array.DOFArray` with the temperature seeds to use in creation of the thermally consistent states. Returns ------- List of :class:`~grudge.trace_pair.TracePair` List of tracepairs of thermally consistent states (:class:`~mirgecom.gas_model.FluidState`) for each boundary in the input set """ from grudge.trace_pair import TracePair if temperature_seed_pairs is None: temperature_seed_pairs = [None] * len(cv_pairs) return [ TracePair(cv_pair.dd, interior=make_fluid_state(cv_pair.int, gas_model, temperature_seed=_getattr_ish( tseed_pair, "int")), exterior=make_fluid_state(cv_pair.ext, gas_model, temperature_seed=_getattr_ish( tseed_pair, "ext"))) for cv_pair, tseed_pair in zip(cv_pairs, temperature_seed_pairs) ]
def v_dot_n_tpair(actx, dcoll, velocity, trace_dd): from grudge.dof_desc import DTAG_BOUNDARY from grudge.trace_pair import TracePair from meshmode.discretization.connection import FACE_RESTR_INTERIOR normal = thaw(dcoll.normal(trace_dd.with_discr_tag(None)), actx) v_dot_n = velocity.dot(normal) i = op.project(dcoll, trace_dd.with_discr_tag(None), trace_dd, v_dot_n) if trace_dd.domain_tag is FACE_RESTR_INTERIOR: e = dcoll.opposite_face_connection()(i) elif isinstance(trace_dd.domain_tag, DTAG_BOUNDARY): e = dcoll.distributed_boundary_swap_connection(trace_dd)(i) else: raise ValueError("Unrecognized domain tag: %s" % trace_dd.domain_tag) return TracePair(trace_dd, interior=i, exterior=e)
def to_quad_int_tpairs(dcoll, u, quad_tag): from grudge.dof_desc import DISCR_TAG_QUAD from grudge.trace_pair import TracePair if issubclass(quad_tag, DISCR_TAG_QUAD): return [ TracePair(tpair.dd.with_discr_tag(quad_tag), interior=op.project(dcoll, tpair.dd, tpair.dd.with_discr_tag(quad_tag), tpair.int), exterior=op.project(dcoll, tpair.dd, tpair.dd.with_discr_tag(quad_tag), tpair.ext)) for tpair in op.interior_trace_pairs(dcoll, u) ] else: return op.interior_trace_pairs(dcoll, u)
def inviscid_divergence_flux(self, discr, btag, gas_model, state_minus, **kwargs): """Get the inviscid boundary flux for the divergence operator.""" # This one is when the user specified a function that directly # prescribes the flux components at the boundary if self._inviscid_bnd_flux_func: return self._inviscid_bnd_flux_func(discr, btag, gas_model, state_minus, **kwargs) state_plus = self._bnd_state_func(discr=discr, btag=btag, gas_model=gas_model, state_minus=state_minus, **kwargs) boundary_state_pair = TracePair(btag, interior=state_minus, exterior=state_plus) return self._inviscid_div_flux_func(discr, state_tpair=boundary_state_pair)
def int_tpair(expression, qtag=None, from_dd=None): from meshmode.discretization.connection import FACE_RESTR_INTERIOR from grudge.symbolic.operators import project, OppositeInteriorFaceSwap import grudge.dof_desc as dof_desc if from_dd is None: from_dd = dof_desc.DD_VOLUME assert not from_dd.uses_quadrature() trace_dd = dof_desc.DOFDesc(FACE_RESTR_INTERIOR, qtag) if from_dd.domain_tag == trace_dd.domain_tag: i = expression else: i = project(from_dd, trace_dd.with_qtag(None))(expression) e = cse(OppositeInteriorFaceSwap()(i)) if trace_dd.uses_quadrature(): i = cse(project(trace_dd.with_qtag(None), trace_dd)(i)) e = cse(project(trace_dd.with_qtag(None), trace_dd)(e)) return TracePair(trace_dd, interior=i, exterior=e)
def inviscid_facial_flux(discr, eos, cv_tpair, local=False): """Return the flux across a face given the solution on both sides *q_tpair*. Parameters ---------- eos: mirgecom.eos.GasEOS Implementing the pressure and temperature functions for returning pressure and temperature as a function of the state q. q_tpair: :class:`grudge.trace_pair.TracePair` Trace pair for the face upon which flux calculation is to be performed local: bool Indicates whether to skip projection of fluxes to "all_faces" or not. If set to *False* (the default), the returned fluxes are projected to "all_faces." If set to *True*, the returned fluxes are not projected to "all_faces"; remaining instead on the boundary restriction. """ actx = cv_tpair.int.array_context flux_tpair = TracePair(cv_tpair.dd, interior=inviscid_flux(discr, eos, cv_tpair.int), exterior=inviscid_flux(discr, eos, cv_tpair.ext)) lam = actx.np.maximum(compute_wavespeed(eos=eos, cv=cv_tpair.int), compute_wavespeed(eos=eos, cv=cv_tpair.ext)) normal = thaw(actx, discr.normal(cv_tpair.dd)) # todo: user-supplied flux routine flux_weak = lfr_flux(cv_tpair, flux_tpair, normal=normal, lam=lam) if local is False: return discr.project(cv_tpair.dd, "all_faces", flux_weak) return flux_weak
def inviscid_facial_flux(discr, state_tpair, local=False): r"""Return the flux across a face given the solution on both sides *q_tpair*. This flux is currently hard-coded to use a Rusanov-type local Lax-Friedrichs (LFR) numerical flux at element boundaries. The numerical inviscid flux $F^*$ is calculated as: .. math:: \mathbf{F}^{*}_{\mathtt{LFR}} = \frac{1}{2}(\mathbf{F}(q^-) +\mathbf{F}(q^+)) \cdot \hat{n} + \frac{\lambda}{2}(q^{-} - q^{+}), where $q^-, q^+$ are the fluid solution state on the interior and the exterior of the face on which the LFR flux is to be calculated, $\mathbf{F}$ is the inviscid fluid flux, $\hat{n}$ is the face normal, and $\lambda$ is the *local* maximum fluid wavespeed. Parameters ---------- discr: :class:`~grudge.eager.EagerDGDiscretization` The discretization collection to use state_tpair: :class:`~grudge.trace_pair.TracePair` Trace pair of :class:`~mirgecom.gas_model.FluidState` for the face upon which the flux calculation is to be performed local: bool Indicates whether to skip projection of fluxes to "all_faces" or not. If set to *False* (the default), the returned fluxes are projected to "all_faces." If set to *True*, the returned fluxes are not projected to "all_faces"; remaining instead on the boundary restriction. Returns ------- :class:`~mirgecom.fluid.ConservedVars` A CV object containing the scalar numerical fluxes at the input faces. The returned fluxes are scalar because they've already been dotted with the face normals as required by the divergence operator for which they are being computed. """ actx = state_tpair.int.array_context dd = state_tpair.dd dd_all_faces = dd.with_dtag("all_faces") flux_tpair = TracePair(dd, interior=inviscid_flux(state_tpair.int), exterior=inviscid_flux(state_tpair.ext)) # This calculates the local maximum eigenvalue of the flux Jacobian # for a single component gas, i.e. the element-local max wavespeed |v| + c. w_int = state_tpair.int.speed_of_sound + state_tpair.int.speed w_ext = state_tpair.ext.speed_of_sound + state_tpair.ext.speed lam = actx.np.maximum(w_int, w_ext) normal = thaw(actx, discr.normal(dd)) cv_tpair = TracePair(dd, interior=state_tpair.int.cv, exterior=state_tpair.ext.cv) # todo: user-supplied flux routine flux_weak = divergence_flux_lfr(cv_tpair, flux_tpair, normal=normal, lam=lam) if local is False: return discr.project(dd, dd_all_faces, flux_weak) return flux_weak
def test_slipwall_flux(actx_factory, dim, order): """Check for zero boundary flux. Check for vanishing flux across the slipwall. """ actx = actx_factory() wall = AdiabaticSlipBoundary() eos = IdealSingleGas() gas_model = GasModel(eos=eos) from pytools.convergence import EOCRecorder eoc = EOCRecorder() for nel_1d in [4, 8, 12]: 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) discr = EagerDGDiscretization(actx, mesh, order=order) nodes = thaw(actx, discr.nodes()) nhat = thaw(actx, discr.normal(BTAG_ALL)) h = 1.0 / nel_1d def bnd_norm(vec): return actx.to_numpy(discr.norm(vec, p=np.inf, dd=BTAG_ALL)) logger.info(f"Number of {dim}d elems: {mesh.nelements}") # for velocities in each direction err_max = 0.0 for vdir in range(dim): vel = np.zeros(shape=(dim, )) # for velocity directions +1, and -1 for parity in [1.0, -1.0]: vel[vdir] = parity from mirgecom.initializers import Uniform initializer = Uniform(dim=dim, velocity=vel) uniform_state = initializer(nodes) fluid_state = make_fluid_state(uniform_state, gas_model) interior_soln = project_fluid_state(discr, "vol", BTAG_ALL, state=fluid_state, gas_model=gas_model) bnd_soln = wall.adiabatic_slip_state(discr, btag=BTAG_ALL, gas_model=gas_model, state_minus=interior_soln) from grudge.trace_pair import TracePair bnd_pair = TracePair(BTAG_ALL, interior=interior_soln.cv, exterior=bnd_soln.cv) state_pair = TracePair(BTAG_ALL, interior=interior_soln, exterior=bnd_soln) # Check the total velocity component normal # to each surface. It should be zero. The # numerical fluxes cannot be zero. avg_state = 0.5 * (bnd_pair.int + bnd_pair.ext) err_max = max(err_max, bnd_norm(np.dot(avg_state.momentum, nhat))) from mirgecom.inviscid import inviscid_facial_flux bnd_flux = inviscid_facial_flux(discr, state_pair, local=True) err_max = max(err_max, bnd_norm(bnd_flux.mass), bnd_norm(bnd_flux.energy)) eoc.add_data_point(h, err_max) message = (f"EOC:\n{eoc}") logger.info(message) assert (eoc.order_estimate() >= order - 0.5 or eoc.max_error() < 1e-12)
def dummy_pair(self, discr, cv, btag, **kwargs): """Get the interior and exterior solution on the boundary.""" dir_soln = self.exterior_q(discr, cv, btag, **kwargs) return TracePair(btag, interior=dir_soln, exterior=dir_soln)
def test_slipwall_identity(actx_factory, dim): """Identity test - check for the expected boundary solution. Checks that the slipwall implements the expected boundary solution: rho_plus = rho_minus v_plus = v_minus - 2 * (n_hat . v_minus) * n_hat mom_plus = rho_plus * v_plus E_plus = E_minus """ actx = actx_factory() 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()) eos = IdealSingleGas() orig = np.zeros(shape=(dim, )) nhat = thaw(actx, discr.normal(BTAG_ALL)) gas_model = GasModel(eos=eos) logger.info(f"Number of {dim}d elems: {mesh.nelements}") # for velocity going along each direction for vdir in range(dim): vel = np.zeros(shape=(dim, )) # for velocity directions +1, and -1 for parity in [1.0, -1.0]: vel[vdir] = parity # Check incoming normal initializer = Lump(dim=dim, center=orig, velocity=vel, rhoamp=0.0) wall = AdiabaticSlipBoundary() uniform_state = initializer(nodes) fluid_state = make_fluid_state(uniform_state, gas_model) def bnd_norm(vec): return actx.to_numpy(discr.norm(vec, p=np.inf, dd=BTAG_ALL)) interior_soln = \ project_fluid_state(discr, "vol", BTAG_ALL, gas_model=gas_model, state=fluid_state) bnd_soln = \ wall.adiabatic_slip_state(discr, btag=BTAG_ALL, gas_model=gas_model, state_minus=interior_soln) from grudge.trace_pair import TracePair bnd_pair = TracePair(BTAG_ALL, interior=interior_soln.cv, exterior=bnd_soln.cv) # check that mass and energy are preserved mass_resid = bnd_pair.int.mass - bnd_pair.ext.mass mass_err = bnd_norm(mass_resid) assert mass_err == 0.0 energy_resid = bnd_pair.int.energy - bnd_pair.ext.energy energy_err = bnd_norm(energy_resid) assert energy_err == 0.0 # check that exterior momentum term is mom_interior - 2 * mom_normal mom_norm_comp = np.dot(bnd_pair.int.momentum, nhat) mom_norm = nhat * mom_norm_comp expected_mom_ext = bnd_pair.int.momentum - 2.0 * mom_norm mom_resid = bnd_pair.ext.momentum - expected_mom_ext mom_err = bnd_norm(mom_resid) assert mom_err == 0.0
def get_gradient_flux(self, discr, quad_tag, dd, alpha, u): # noqa: D102 u_int = discr.project("vol", dd, u) u_tpair = TracePair(dd, interior=u_int, exterior=u_int) return gradient_flux(discr, quad_tag, u_tpair)
def boundary_pair(self, discr, cv, btag, **kwargs): """Get the interior and exterior solution on the boundary.""" dir_soln = discr.project("vol", btag, cv) return TracePair(btag, interior=dir_soln, exterior=dir_soln)