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 cross_rank_trace_pairs(discrwb, vec, tag=None): if (isinstance(vec, np.ndarray) and vec.dtype.char == "O" and not isinstance(vec, DOFArray)): n, = vec.shape result = {} for ivec in range(n): for rank_tpair in _cross_rank_trace_pairs_scalar_field( discrwb, vec[ivec]): assert isinstance(rank_tpair.dd.domain_tag, sym.DTAG_BOUNDARY) assert isinstance(rank_tpair.dd.domain_tag.tag, BTAG_PARTITION) result[rank_tpair.dd.domain_tag.tag.part_nr, ivec] = rank_tpair return [ TracePair( dd=sym.as_dofdesc(sym.DTAG_BOUNDARY(BTAG_PARTITION(remote_rank))), interior=make_obj_array([ result[remote_rank, i].int for i in range(n)]), exterior=make_obj_array([ result[remote_rank, i].ext for i in range(n)]) ) for remote_rank in discrwb.connected_ranks()] else: return _cross_rank_trace_pairs_scalar_field(discrwb, vec, tag=tag)
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(dcoll, c, w): u = w[0] v = w[1:] dir_u = op.project(dcoll, "vol", BTAG_ALL, u) dir_v = op.project(dcoll, "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", DISCR_TAG_QUAD) c_quad = op.project(dcoll, "vol", dd_quad, c) w_quad = op.project(dcoll, "vol", dd_quad, w) u_quad = w_quad[0] v_quad = w_quad[1:] dd_allfaces_quad = DOFDesc("all_faces", DISCR_TAG_QUAD) return (op.inverse_mass( dcoll, flat_obj_array(-op.weak_local_div(dcoll, dd_quad, c_quad * v_quad), -op.weak_local_grad(dcoll, dd_quad, c_quad * u_quad)) + # noqa: W504 op.face_mass( dcoll, dd_allfaces_quad, wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) + wave_flux(dcoll, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)))))
def interior_trace_pair(discrwb, vec): """Return a :class:`grudge.sym.TracePair` for the interior faces of *discrwb*. """ i = discrwb.project("vol", "int_faces", vec) e = obj_array_vectorize(lambda el: discrwb.opposite_face_connection()(el), i) return TracePair("int_faces", interior=i, exterior=e)
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 boundary_pair(self, discr, q, btag, eos, t=0.0): """Get the interior and exterior solution on the boundary.""" actx = q[0].array_context boundary_discr = discr.discr_from_dd(btag) nodes = thaw(actx, boundary_discr.nodes()) ext_soln = self._userfunc(t, nodes) int_soln = discr.project("vol", btag, q) return TracePair(btag, interior=int_soln, exterior=ext_soln)
def boundary_pair(self, discr, q, t=0.0, btag=BTAG_ALL, eos=IdealSingleGas()): """Get the interior and exterior solution on the boundary.""" dir_soln = discr.project("vol", btag, q) return TracePair(btag, interior=dir_soln, exterior=dir_soln)
def interior_trace_pair(discrwb, vec): i = discrwb.project("vol", "int_faces", vec) if (isinstance(vec, np.ndarray) and vec.dtype.char == "O" and not isinstance(vec, DOFArray)): e = obj_array_vectorize( lambda el: discrwb.opposite_face_connection()(el), i) return TracePair("int_faces", i, e)
def _cross_rank_trace_pairs_scalar_field(dcoll, vec, tag=None): if isinstance(vec, Number): return [ TracePair(BTAG_PARTITION(remote_rank), interior=vec, exterior=vec) for remote_rank in connected_ranks(dcoll) ] else: rbcomms = [ _RankBoundaryCommunication(dcoll, remote_rank, vec, tag=tag) for remote_rank in connected_ranks(dcoll) ] return [rbcomm.finish() for rbcomm in rbcomms]
def interior_trace_pair(dcoll, vec): """Return a :class:`grudge.sym.TracePair` for the interior faces of *dcoll*. """ i = project(dcoll, "vol", "int_faces", vec) def get_opposite_face(el): if isinstance(el, Number): return el else: return dcoll.opposite_face_connection()(el) e = obj_array_vectorize(get_opposite_face, i) return TracePair("int_faces", interior=i, exterior=e)
def finish(self): self.recv_req.Wait() actx = self.array_context remote_dof_array = unflatten(self.array_context, self.bdry_discr, actx.from_numpy(self.remote_data_host)) bdry_conn = self.discrwb.get_distributed_boundary_swap_connection( sym.as_dofdesc(sym.DTAG_BOUNDARY(self.remote_btag))) swapped_remote_dof_array = bdry_conn(remote_dof_array) self.send_req.Wait() return TracePair(self.remote_btag, self.local_dof_array, swapped_remote_dof_array)
def cross_rank_trace_pairs(dcoll, ary, tag=None): r"""Get a list of *ary* trace pairs for each partition boundary. For each partition boundary, the field data values in *ary* are communicated to/from the neighboring partition. Presumably, this communication is MPI (but strictly speaking, may not be, and this routine is agnostic to the underlying communication, see e.g. _cross_rank_trace_pairs_scalar_field). For each face on each partition boundary, a :class:`TracePair` is created with the locally, and remotely owned partition boundary face data as the `internal`, and `external` components, respectively. Each of the TracePair components are structured like *ary*. The input field data *ary* may be a single :class:`~meshmode.dof_array.DOFArray`, or an object array of ``DOFArray``\ s of arbitrary shape. """ if isinstance(ary, np.ndarray): oshape = ary.shape comm_vec = ary.flatten() n, = comm_vec.shape result = {} # FIXME: Batch this communication rather than # doing it in sequence. for ivec in range(n): for rank_tpair in _cross_rank_trace_pairs_scalar_field( dcoll, comm_vec[ivec]): assert isinstance(rank_tpair.dd.domain_tag, dof_desc.DTAG_BOUNDARY) assert isinstance(rank_tpair.dd.domain_tag.tag, BTAG_PARTITION) result[rank_tpair.dd.domain_tag.tag.part_nr, ivec] = rank_tpair return [ TracePair(dd=dof_desc.as_dofdesc( dof_desc.DTAG_BOUNDARY(BTAG_PARTITION(remote_rank))), interior=make_obj_array( [result[remote_rank, i].int for i in range(n)]).reshape(oshape), exterior=make_obj_array( [result[remote_rank, i].ext for i in range(n)]).reshape(oshape)) for remote_rank in connected_ranks(dcoll) ] else: return _cross_rank_trace_pairs_scalar_field(dcoll, ary, tag=tag)
def wave_operator(dcoll, c, w): u = w[0] v = w[1:] dir_u = op.project(dcoll, "vol", BTAG_ALL, u) dir_v = op.project(dcoll, "vol", BTAG_ALL, v) dir_bval = flat_obj_array(dir_u, dir_v) dir_bc = flat_obj_array(-dir_u, dir_v) return (op.inverse_mass( dcoll, flat_obj_array(-c * op.weak_local_div(dcoll, v), -c * op.weak_local_grad(dcoll, u)) + # noqa: W504 op.face_mass( dcoll, wave_flux(dcoll, c=c, w_tpair=op.interior_trace_pair(dcoll, w)) + wave_flux(dcoll, c=c, w_tpair=TracePair( BTAG_ALL, interior=dir_bval, exterior=dir_bc)))))
def boundary_pair(self, discr, q, btag, eos, t=0.0): """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 cv = split_conserved(dim, q) actx = cv.mass.array_context # Grab a unit normal to the boundary nhat = thaw(actx, discr.normal(btag)) # Get the interior/exterior solns int_soln = discr.project("vol", btag, q) int_cv = split_conserved(dim, int_soln) # 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 bndry_soln = join_conserved(dim=dim, mass=int_cv.mass, energy=int_cv.energy, momentum=ext_mom) return TracePair(btag, interior=int_soln, exterior=bndry_soln)
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 get_u_flux(self, discr, alpha, dd, q): # noqa: D102 q_int = discr.project("vol", dd, q) q_tpair = TracePair(dd, interior=q_int, exterior=q_int) return _u_flux(discr, alpha, q_tpair)
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 boundary_pair(self, discr, q, btag, eos, t=0.0): """Get the interior and exterior solution on the boundary.""" dir_soln = discr.project("vol", btag, q) return TracePair(btag, interior=dir_soln, exterior=dir_soln)
def test_facial_flux(actx_factory, nspecies, order, dim): """Check the flux across element faces. The flux is checked 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 grudge.trace_pair import interior_trace_pairs cv_interior_pairs = interior_trace_pairs(discr, cv) # Check the boundary facial fluxes as called on an interior boundary # eos = IdealSingleGas() from mirgecom.gas_model import (GasModel, make_fluid_state) gas_model = GasModel(eos=IdealSingleGas()) from mirgecom.gas_model import make_fluid_state_trace_pairs state_tpairs = make_fluid_state_trace_pairs(cv_interior_pairs, gas_model) interior_state_pair = state_tpairs[0] from mirgecom.inviscid import inviscid_facial_flux interior_face_flux = \ inviscid_facial_flux(discr, state_tpair=interior_state_pair) def inf_norm(data): if len(data) > 0: return actx.to_numpy(discr.norm(data, np.inf, dd="all_faces")) else: return 0.0 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_bc = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) dir_bval = make_conserved(dim, mass=dir_mass, energy=dir_e, momentum=dir_mom, species_mass=dir_mf) state_tpair = TracePair(BTAG_ALL, interior=make_fluid_state(dir_bval, gas_model), exterior=make_fluid_state(dir_bc, gas_model)) boundary_flux = inviscid_facial_flux(discr, state_tpair=state_tpair) 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 get_q_flux(self, discr, alpha, dd, u): # noqa: D102 u_int = discr.project("vol", dd, u) u_tpair = TracePair(dd, interior=u_int, exterior=u_int) return _q_flux(discr, alpha, u_tpair)