def wave_operator(discr, c, w): u = w[0] v = w[1:] dir_u = discr.project("vol", BTAG_ALL, u) dir_v = discr.project("vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) dd_quad = DOFDesc("vol", "vel_prod") c_quad = discr.project("vol", dd_quad, c) w_quad = discr.project("vol", dd_quad, w) u_quad = w_quad[0] v_quad = w_quad[1:] dd_allfaces_quad = DOFDesc("all_faces", "vel_prod") # FIXME Fix sign issue return (discr.inverse_mass( flat_obj_array(discr.weak_div(dd_quad, scalar(c_quad) * v_quad), discr.weak_grad(dd_quad, c_quad * u_quad)) - # noqa: W504 discr.face_mass( dd_allfaces_quad, wave_flux(discr, c=c, w_tpair=interior_trace_pair(discr, w)) + wave_flux( discr, c=c, w_tpair=TracePair(BTAG_ALL, dir_bval, dir_bc)))))
def wave_operator(discr, c, w): u = w[0] v = w[1:] dir_u = discr.project("vol", BTAG_ALL, u) dir_v = discr.project("vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) return ( discr.inverse_mass( flat_obj_array( -c*discr.weak_div(v), -c*discr.weak_grad(u) ) + # noqa: W504 discr.face_mass( wave_flux(discr, c=c, w_tpair=interior_trace_pair(discr, w)) + wave_flux(discr, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)) + sum( wave_flux(discr, c=c, w_tpair=tpair) for tpair in cross_rank_trace_pairs(discr, w)) ) ) )
def inviscid_operator(discr, eos, boundaries, q, 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}} = \mathbf{S} - \nabla\cdot\mathbf{F} + (\mathbf{F}\cdot\hat{n})_{\partial\Omega} Parameters ---------- q State array which expects at least the canonical conserved quantities (mass, energy, momentum) for the fluid at each point. 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. """ vol_flux = inviscid_flux(discr, eos, q) dflux = discr.weak_div(vol_flux) interior_face_flux = _facial_flux( discr, eos=eos, q_tpair=interior_trace_pair(discr, q)) # Domain boundaries domain_boundary_flux = sum( _facial_flux( discr, q_tpair=boundaries[btag].boundary_pair(discr, eos=eos, btag=btag, t=t, q=q), eos=eos ) for btag in boundaries ) # Flux across partition boundaries partition_boundary_flux = sum( _facial_flux(discr, eos=eos, q_tpair=part_pair) for part_pair in cross_rank_trace_pairs(discr, q) ) return discr.inverse_mass( dflux - discr.face_mass(interior_face_flux + domain_boundary_flux + partition_boundary_flux) )
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 wave_operator(discr, c, w): """Compute the RHS of the wave equation. Parameters ---------- discr: grudge.eager.EagerDGDiscretization the discretization to use c: float the (constant) wave speed w: numpy.ndarray an object array of DOF arrays, representing the state vector Returns ------- numpy.ndarray an object array of DOF arrays, representing the ODE RHS """ u = w[0] v = w[1:] dir_u = discr.project("vol", BTAG_ALL, u) dir_v = discr.project("vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) return (discr.inverse_mass( flat_obj_array(-c * discr.weak_div(v), -c * discr.weak_grad(u)) + # noqa: W504 discr.face_mass( _flux(discr, c=c, w_tpair=interior_trace_pair(discr, w)) + _flux(discr, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)) + sum( _flux(discr, c=c, w_tpair=tpair) for tpair in cross_rank_trace_pairs(discr, w)))))
def test_facial_flux(actx_factory, order, dim): """Check the flux across element faces by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, n=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) fields = join_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input) from mirgecom.euler import _facial_flux interior_face_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=interior_trace_pair( discr, fields)) from functools import partial fnorm = partial(discr.norm, p=np.inf, dd="all_faces") iff_split = split_conserved(dim, interior_face_flux) assert fnorm(iff_split.mass) < tolerance assert fnorm(iff_split.energy) < tolerance # The expected pressure 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) momerr = fnorm(iff_split.momentum) - p0 assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_bval = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) dir_bc = join_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom) boundary_flux = _facial_flux(discr, eos=IdealSingleGas(), q_tpair=TracePair(BTAG_ALL, interior=dir_bval, exterior=dir_bc)) bf_split = split_conserved(dim, boundary_flux) assert fnorm(bf_split.mass) < tolerance assert fnorm(bf_split.energy) < tolerance momerr = fnorm(bf_split.momentum) - p0 assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) message = (f"standalone Errors:\n{eoc_rec0}" f"boundary Errors:\n{eoc_rec1}") logger.info(message) assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
def diffusion_operator(discr, quad_tag, alpha, boundaries, u, return_grad_u=False): r""" Compute the diffusion operator. The diffusion operator is defined as $\nabla\cdot(\alpha\nabla u)$, where $\alpha$ is the diffusivity and $u$ is a scalar field. Uses unstabilized central numerical fluxes. Parameters ---------- discr: grudge.eager.EagerDGDiscretization the discretization to use quad_tag: quadrature tag indicating which discretization in *discr* to use for overintegration alpha: numbers.Number or meshmode.dof_array.DOFArray the diffusivity value(s) boundaries: dictionary (or list of dictionaries) mapping boundary tags to :class:`DiffusionBoundary` instances u: meshmode.dof_array.DOFArray or numpy.ndarray the DOF array (or object array of DOF arrays) to which the operator should be applied return_grad_u: bool an optional flag indicating whether $\nabla u$ should also be returned Returns ------- diff_u: meshmode.dof_array.DOFArray or numpy.ndarray the diffusion operator applied to *u* grad_u: numpy.ndarray the gradient of *u*; only returned if *return_grad_u* is True """ if isinstance(u, np.ndarray): if not isinstance(boundaries, list): raise TypeError( "boundaries must be a list if u is an object array") if len(boundaries) != len(u): raise TypeError("boundaries must be the same length as u") return obj_array_vectorize_n_args( lambda boundaries_i, u_i: diffusion_operator(discr, quad_tag, alpha, boundaries_i, u_i, return_grad_u= return_grad_u), make_obj_array(boundaries), u) for btag, bdry in boundaries.items(): if not isinstance(bdry, DiffusionBoundary): raise TypeError(f"Unrecognized boundary type for tag {btag}. " "Must be an instance of DiffusionBoundary.") dd_quad = DOFDesc("vol", quad_tag) dd_allfaces_quad = DOFDesc("all_faces", quad_tag) grad_u = discr.inverse_mass( discr.weak_grad(-u) - # noqa: W504 discr.face_mass( dd_allfaces_quad, gradient_flux(discr, quad_tag, interior_trace_pair(discr, u)) + sum( bdry.get_gradient_flux(discr, quad_tag, as_dofdesc(btag), alpha, u) for btag, bdry in boundaries.items()) + sum( gradient_flux(discr, quad_tag, u_tpair) for u_tpair in cross_rank_trace_pairs(discr, u)))) alpha_quad = discr.project("vol", dd_quad, alpha) grad_u_quad = discr.project("vol", dd_quad, grad_u) diff_u = discr.inverse_mass( discr.weak_div(dd_quad, -alpha_quad * grad_u_quad) - # noqa: W504 discr.face_mass( dd_allfaces_quad, diffusion_flux(discr, quad_tag, interior_trace_pair(discr, alpha), interior_trace_pair(discr, grad_u)) + sum( bdry.get_diffusion_flux(discr, quad_tag, as_dofdesc(btag), alpha, grad_u) for btag, bdry in boundaries.items()) + sum( diffusion_flux(discr, quad_tag, alpha_tpair, grad_u_tpair) for alpha_tpair, grad_u_tpair in zip( cross_rank_trace_pairs(discr, alpha), cross_rank_trace_pairs(discr, grad_u))))) if return_grad_u: return diff_u, grad_u else: return diff_u
def diffusion_operator(discr, alpha, boundaries, u): r""" Compute the diffusion operator. The diffusion operator is defined as $\nabla\cdot(\alpha\nabla u)$, where $\alpha$ is the diffusivity and $u$ is a scalar field. Parameters ---------- discr: grudge.eager.EagerDGDiscretization the discretization to use alpha: float the (constant) diffusivity boundaries: dictionary (or list of dictionaries) mapping boundary tags to :class:`DiffusionBoundary` instances u: meshmode.dof_array.DOFArray or numpy.ndarray the DOF array (or object array of DOF arrays) to which the operator should be applied Returns ------- meshmode.dof_array.DOFArray or numpy.ndarray the diffusion operator applied to *u* """ if isinstance(u, np.ndarray): if not isinstance(boundaries, list): raise TypeError( "boundaries must be a list if u is an object array") if len(boundaries) != len(u): raise TypeError("boundaries must be the same length as u") return obj_array_vectorize_n_args( lambda boundaries_i, u_i: diffusion_operator( discr, alpha, boundaries_i, u_i), make_obj_array(boundaries), u) for btag, bdry in boundaries.items(): if not isinstance(bdry, DiffusionBoundary): raise TypeError(f"Unrecognized boundary type for tag {btag}. " "Must be an instance of DiffusionBoundary.") q = discr.inverse_mass( -math.sqrt(alpha) * discr.weak_grad(u) + # noqa: W504 discr.face_mass( _q_flux(discr, alpha=alpha, u_tpair=interior_trace_pair(discr, u)) + sum( bdry.get_q_flux(discr, alpha=alpha, dd=btag, u=u) for btag, bdry in boundaries.items()) + sum( _q_flux(discr, alpha=alpha, u_tpair=tpair) for tpair in cross_rank_trace_pairs(discr, u)))) return (discr.inverse_mass( -math.sqrt(alpha) * discr.weak_div(q) + # noqa: W504 discr.face_mass( _u_flux(discr, alpha=alpha, q_tpair=interior_trace_pair(discr, q)) + sum( bdry.get_u_flux(discr, alpha=alpha, dd=btag, q=q) for btag, bdry in boundaries.items()) + sum( _u_flux(discr, alpha=alpha, q_tpair=tpair) for tpair in cross_rank_trace_pairs(discr, q)))))
def test_facial_flux(actx_factory, nspecies, order, dim): """Check the flux across element faces by prescribing states (q) with known fluxes. Only uniform states are tested currently - ensuring that the Lax-Friedrichs flux terms which are proportional to jumps in state data vanish. Since the returned fluxes use state data which has been interpolated to-and-from the element faces, this test is grid-dependent. """ actx = actx_factory() tolerance = 1e-14 p0 = 1.0 from meshmode.mesh.generation import generate_regular_rect_mesh from pytools.convergence import EOCRecorder eoc_rec0 = EOCRecorder() eoc_rec1 = EOCRecorder() for nel_1d in [4, 8, 12]: mesh = generate_regular_rect_mesh(a=(-0.5, ) * dim, b=(0.5, ) * dim, nelements_per_axis=(nel_1d, ) * dim) logger.info(f"Number of elements: {mesh.nelements}") discr = EagerDGDiscretization(actx, mesh, order=order) zeros = discr.zeros(actx) ones = zeros + 1.0 mass_input = discr.zeros(actx) + 1.0 energy_input = discr.zeros(actx) + 2.5 mom_input = flat_obj_array( [discr.zeros(actx) for i in range(discr.dim)]) mass_frac_input = flat_obj_array( [ones / ((i + 1) * 10) for i in range(nspecies)]) species_mass_input = mass_input * mass_frac_input cv = make_conserved(dim, mass=mass_input, energy=energy_input, momentum=mom_input, species_mass=species_mass_input) from mirgecom.euler import _facial_flux # Check the boundary facial fluxes as called on an interior boundary interior_face_flux = _facial_flux(discr, eos=IdealSingleGas(), cv_tpair=interior_trace_pair( discr, cv)) def inf_norm(data): if len(data) > 0: return discr.norm(data, np.inf, dd="all_faces") else: return 0.0 # iff_split = split_conserved(dim, interior_face_flux) assert inf_norm(interior_face_flux.mass) < tolerance assert inf_norm(interior_face_flux.energy) < tolerance assert inf_norm(interior_face_flux.species_mass) < tolerance # The expected pressure is 1.0 (by design). And the flux diagonal is # [rhov_x*v_x + p] (etc) since we have zero velocities it's just p. # # The off-diagonals are zero. We get a {ndim}-vector for each # dimension, the flux for the x-component of momentum (for example) is: # f_momx = < 1.0, 0 , 0> , then we return f_momx .dot. normal, which # can introduce negative values. # # (Explanation courtesy of Mike Campbell, # https://github.com/illinois-ceesd/mirgecom/pull/44#discussion_r463304292) nhat = thaw(actx, discr.normal("int_faces")) mom_flux_exact = discr.project("int_faces", "all_faces", p0 * nhat) print(f"{mom_flux_exact=}") print(f"{interior_face_flux.momentum=}") momerr = inf_norm(interior_face_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec0.add_data_point(1.0 / nel_1d, momerr) # Check the boundary facial fluxes as called on a domain boundary dir_mass = discr.project("vol", BTAG_ALL, mass_input) dir_e = discr.project("vol", BTAG_ALL, energy_input) dir_mom = discr.project("vol", BTAG_ALL, mom_input) dir_mf = discr.project("vol", BTAG_ALL, species_mass_input) dir_bval = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) dir_bc = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) boundary_flux = _facial_flux(discr, eos=IdealSingleGas(), cv_tpair=TracePair(BTAG_ALL, interior=dir_bval, exterior=dir_bc)) assert inf_norm(boundary_flux.mass) < tolerance assert inf_norm(boundary_flux.energy) < tolerance assert inf_norm(boundary_flux.species_mass) < tolerance nhat = thaw(actx, discr.normal(BTAG_ALL)) mom_flux_exact = discr.project(BTAG_ALL, "all_faces", p0 * nhat) momerr = inf_norm(boundary_flux.momentum - mom_flux_exact) assert momerr < tolerance eoc_rec1.add_data_point(1.0 / nel_1d, momerr) logger.info(f"standalone Errors:\n{eoc_rec0}" f"boundary Errors:\n{eoc_rec1}") assert (eoc_rec0.order_estimate() >= order - 0.5 or eoc_rec0.max_error() < 1e-9) assert (eoc_rec1.order_estimate() >= order - 0.5 or eoc_rec1.max_error() < 1e-9)
def test_grad_operator(actx_factory, dim, order, sym_test_func_factory): """Test the gradient operator for sanity. Check whether we get the right answers for gradients of analytic functions with some simple input fields and states: - constant - multilinear funcs - quadratic funcs - trig funcs - :class:`~mirgecom.fluid.ConservedVars` composed of funcs from above """ actx = actx_factory() sym_test_func = sym_test_func_factory(dim) tol = 1e-10 if dim < 3 else 1e-9 from pytools.convergence import EOCRecorder eoc = EOCRecorder() for nfac in [1, 2, 4]: npts_axis = (nfac * 4, ) * dim box_ll = (0, ) * dim box_ur = (1, ) * dim mesh = _get_box_mesh(dim, 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) # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(discr) def sym_eval(expr, x_vec): # FIXME: When pymbolic supports array containers mapper = sym.EvaluationMapper({"x": x_vec}) from arraycontext import rec_map_array_container result = rec_map_array_container(mapper, expr) # If expressions don't depend on coords (e.g., order 0), evaluated result # will be scalar-valued, so promote to DOFArray(s) before returning return result * (0 * x_vec[0] + 1) test_func = partial(sym_eval, sym_test_func) grad_test_func = partial(sym_eval, sym_grad(dim, sym_test_func)) nodes = thaw(actx, discr.nodes()) int_flux = partial(central_flux_interior, actx, discr) bnd_flux = partial(central_flux_boundary, actx, discr, test_func) test_data = test_func(nodes) exact_grad = grad_test_func(nodes) from mirgecom.simutil import componentwise_norms from arraycontext import flatten err_scale = max( flatten(componentwise_norms(discr, exact_grad, np.inf), actx)) if err_scale <= 1e-16: err_scale = 1 print(f"{test_data=}") print(f"{exact_grad=}") test_data_int_tpair = interior_trace_pair(discr, test_data) boundaries = [BTAG_ALL] test_data_flux_bnd = _elbnd_flux(discr, int_flux, bnd_flux, test_data_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") test_grad = grad_operator(discr, dd_vol, dd_faces, test_data, test_data_flux_bnd) print(f"{test_grad=}") grad_err = \ max(flatten(componentwise_norms(discr, test_grad - exact_grad, np.inf), actx)) / err_scale eoc.add_data_point(actx.to_numpy(h_max), actx.to_numpy(grad_err)) assert (eoc.order_estimate() >= order - 0.5 or eoc.max_error() < tol)
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)