def sym_operator(self): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.discretization.connection import FACE_RESTR_ALL u = sym.var("u") def flux(pair): return sym.project(pair.dd, face_dd)(self.flux(pair)) face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) to_quad = sym.project(DD_VOLUME, quad_dd) stiff_t_op = sym.stiffness_t(self.ambient_dim, dd_in=quad_dd, dd_out=DD_VOLUME) quad_v = to_quad(self.v) quad_u = to_quad(u) return sym.InverseMassOperator()( sum(stiff_t_op[n](quad_u * quad_v[n]) for n in range(self.ambient_dim)) - sym.FaceMassOperator(face_dd, DD_VOLUME) (flux(sym.int_tpair(u, self.quad_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) 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 sym_operator(self): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.mesh import BTAG_ALL from meshmode.discretization.connection import FACE_RESTR_ALL u = sym.var("u") def flux(pair): return sym.project(pair.dd, face_dd)(self.flux(pair)) face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) boundary_dd = DOFDesc(BTAG_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) to_quad = sym.project(DD_VOLUME, quad_dd) stiff_t_op = sym.stiffness_t(self.ambient_dim, dd_in=quad_dd, dd_out=DD_VOLUME) quad_v = to_quad(self.v) quad_u = to_quad(u) return sym.InverseMassOperator()( sum(stiff_t_op[n](quad_u * quad_v[n]) for n in range(self.ambient_dim)) - sym.FaceMassOperator(face_dd, DD_VOLUME)( flux(sym.int_tpair(u, self.quad_tag)) + flux(sym.bv_tpair(boundary_dd, u, self.inflow_u)) # FIXME: Add back support for inflow/outflow tags #+ flux(sym.bv_tpair(self.inflow_tag, u, bc_in)) #+ flux(sym.bv_tpair(self.outflow_tag, u, bc_out)) ))
def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.discretization.connection import FACE_RESTR_ALL face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) dcoll = self.dcoll def flux(tpair): return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) def to_quad(arg): return op.project(dcoll, DD_VOLUME, quad_dd, arg) quad_v = to_quad(self.v) quad_u = to_quad(u) return (op.inverse_mass( dcoll, sum( op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) for d in range(dcoll.ambient_dim)) - op.face_mass( dcoll, face_dd, sum( flux(quad_tpair) for quad_tpair in to_quad_int_tpairs( dcoll, u, self.quad_tag)))))
def operator(self, t, u): from grudge.dof_desc import DOFDesc, DD_VOLUME, DTAG_VOLUME_ALL from meshmode.mesh import BTAG_ALL from meshmode.discretization.connection import FACE_RESTR_ALL face_dd = DOFDesc(FACE_RESTR_ALL, self.quad_tag) boundary_dd = DOFDesc(BTAG_ALL, self.quad_tag) quad_dd = DOFDesc(DTAG_VOLUME_ALL, self.quad_tag) dcoll = self.dcoll def flux(tpair): return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair)) def to_quad(arg): return op.project(dcoll, DD_VOLUME, quad_dd, arg) if self.inflow_u is not None: inflow_flux = flux( op.bv_trace_pair(dcoll, boundary_dd, interior=u, exterior=self.inflow_u(t))) else: inflow_flux = 0 quad_v = to_quad(self.v) quad_u = to_quad(u) return (op.inverse_mass( dcoll, sum( op.weak_local_d_dx(dcoll, quad_dd, d, quad_u * quad_v[d]) for d in range(dcoll.ambient_dim)) - op.face_mass( dcoll, face_dd, sum( flux(quad_tpair) for quad_tpair in to_quad_int_tpairs( dcoll, u, self.quad_tag)) + inflow_flux # FIXME: Add support for inflow/outflow tags # + flux(op.bv_trace_pair(dcoll, # self.inflow_tag, # interior=u, # exterior=bc_in)) # + flux(op.bv_trace_pair(dcoll, # self.outflow_tag, # interior=u, # exterior=bc_out)) )))
def v_dot_n_tpair(velocity, dd=None): from grudge.dof_desc import DOFDesc from meshmode.discretization.connection import FACE_RESTR_INTERIOR if dd is None: dd = DOFDesc(FACE_RESTR_INTERIOR) ambient_dim = len(velocity) normal = sym.normal(dd.with_discr_tag(None), ambient_dim, dim=ambient_dim - 2) return sym.int_tpair(velocity.dot(normal), qtag=dd.discretization_tag, from_dd=dd.with_discr_tag(None))
def _modal_discr(self, domain_tag): from meshmode.discretization import Discretization discr_base = self.discr_from_dd(DOFDesc(domain_tag, DISCR_TAG_BASE)) return Discretization( self._setup_actx, discr_base.mesh, self.group_factory_for_discretization_tag(DISCR_TAG_MODAL))
def discr_from_dd(self, dd): dd = as_dofdesc(dd) discr_tag = dd.discretization_tag if discr_tag is DISCR_TAG_MODAL: return self._modal_discr(dd.domain_tag) if dd.is_volume(): if discr_tag is not DISCR_TAG_BASE: return self._discr_tag_volume_discr(discr_tag) return self._volume_discr if discr_tag is not DISCR_TAG_BASE: no_quad_discr = self.discr_from_dd(DOFDesc(dd.domain_tag)) from meshmode.discretization import Discretization return Discretization( self._setup_actx, no_quad_discr.mesh, self.group_factory_for_discretization_tag(discr_tag)) assert discr_tag is DISCR_TAG_BASE if dd.domain_tag is FACE_RESTR_ALL: return self._all_faces_volume_connection().to_discr elif dd.domain_tag is FACE_RESTR_INTERIOR: return self._interior_faces_connection().to_discr elif dd.is_boundary_or_partition_interface(): return self._boundary_connection(dd.domain_tag.tag).to_discr else: raise ValueError("DOF desc tag not understood: " + str(dd))
def map_signed_face_ones(self, expr): from grudge.dof_desc import DOFDesc, DD_VOLUME assert expr.dd.is_trace() face_discr = self.dcoll.discr_from_dd(expr.dd) assert face_discr.dim == 0 # NOTE: ignore quadrature_tags on expr.dd, since we only care about # the face_id here all_faces_conn = self.dcoll.connection_from_dds( DD_VOLUME, DOFDesc(expr.dd.domain_tag)) field = face_discr.empty(self.array_context, dtype=self.dcoll.real_dtype) for grp_ary in field: grp_ary.fill(1) for igrp, grp in enumerate(all_faces_conn.groups): for batch in grp.batches: i = self.array_context.thaw(batch.to_element_indices) grp_field = field[igrp].reshape(-1) grp_field[i] = \ (2.0 * (batch.to_element_face % 2) - 1.0) * grp_field[i] return field
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) \ # pylint: disable=invalid-unary-operand-type ) + op.face_mass( dcoll, dd_allfaces_quad, wave_flux( dcoll, c=c, w_tpair=op.bdry_trace_pair(dcoll, BTAG_ALL, interior=dir_bval, exterior=dir_bc) ) + sum( wave_flux(dcoll, c=c, w_tpair=tpair) for tpair in op.interior_trace_pairs(dcoll, w) ) ) ) )
def _set_up_distributed_communication(self, mpi_communicator, array_context): from_dd = DOFDesc("vol", DISCR_TAG_BASE) boundary_connections = {} from meshmode.distributed import get_connected_partitions connected_parts = get_connected_partitions(self._volume_discr.mesh) if connected_parts: if mpi_communicator is None: raise RuntimeError( "must supply an MPI communicator when using a " "distributed mesh") grp_factory = \ self.group_factory_for_discretization_tag(DISCR_TAG_BASE) local_boundary_connections = {} for i_remote_part in connected_parts: local_boundary_connections[ i_remote_part] = self.connection_from_dds( from_dd, DOFDesc(BTAG_PARTITION(i_remote_part), DISCR_TAG_BASE)) from meshmode.distributed import MPIBoundaryCommSetupHelper with MPIBoundaryCommSetupHelper(mpi_communicator, array_context, local_boundary_connections, grp_factory) as bdry_setup_helper: while True: conns = bdry_setup_helper.complete_some() if not conns: break for i_remote_part, conn in conns.items(): boundary_connections[i_remote_part] = conn return boundary_connections
def _signed_face_ones(actx: ArrayContext, dcoll: DiscretizationCollection, dd) -> DOFArray: assert dd.is_trace() # NOTE: ignore quadrature_tags on dd, since we only care about # the face_id here all_faces_conn = dcoll.connection_from_dds(DD_VOLUME, DOFDesc(dd.domain_tag)) signed_face_ones = dcoll.discr_from_dd(dd).zeros( actx, dtype=dcoll.real_dtype) + 1 for igrp, grp in enumerate(all_faces_conn.groups): for batch in grp.batches: i = actx.thaw(batch.to_element_indices) grp_field = signed_face_ones[igrp].reshape(-1) grp_field[i] = \ (2.0 * (batch.to_element_face % 2) - 1.0) * grp_field[i] return signed_face_ones
def connection_from_dds(self, from_dd, to_dd): """Provides a mapping (connection) from one discretization to another, e.g. from the volume to the boundary, or from the base to the an overintegrated quadrature discretization, or from a nodal representation to a modal representation. :arg from_dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. :arg to_dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. """ from_dd = as_dofdesc(from_dd) to_dd = as_dofdesc(to_dd) to_discr_tag = to_dd.discretization_tag from_discr_tag = from_dd.discretization_tag # {{{ mapping between modal and nodal representations if (from_discr_tag is DISCR_TAG_MODAL and to_discr_tag is not DISCR_TAG_MODAL): return self._modal_to_nodal_connection(to_dd) if (to_discr_tag is DISCR_TAG_MODAL and from_discr_tag is not DISCR_TAG_MODAL): return self._nodal_to_modal_connection(from_dd) # }}} assert (to_discr_tag is not DISCR_TAG_MODAL and from_discr_tag is not DISCR_TAG_MODAL) if (not from_dd.is_volume() and from_discr_tag == to_discr_tag and to_dd.domain_tag is FACE_RESTR_ALL): faces_conn = self.connection_from_dds(DOFDesc("vol"), DOFDesc(from_dd.domain_tag)) from meshmode.discretization.connection import \ make_face_to_all_faces_embedding return make_face_to_all_faces_embedding( self._setup_actx, faces_conn, self.discr_from_dd(to_dd), self.discr_from_dd(from_dd)) # {{{ simplify domain + discr_tag change into chained if (from_dd.domain_tag != to_dd.domain_tag and from_discr_tag is DISCR_TAG_BASE and to_discr_tag is not DISCR_TAG_BASE): from meshmode.discretization.connection import \ ChainedDiscretizationConnection intermediate_dd = DOFDesc(to_dd.domain_tag) return ChainedDiscretizationConnection([ # first change domain self.connection_from_dds(from_dd, intermediate_dd), # then go to quad grid self.connection_from_dds(intermediate_dd, to_dd) ]) # }}} # {{{ generic to-quad # DISCR_TAG_MODAL is handled above if (from_dd.domain_tag == to_dd.domain_tag and from_discr_tag is DISCR_TAG_BASE and to_discr_tag is not DISCR_TAG_BASE): from meshmode.discretization.connection.same_mesh import \ make_same_mesh_connection return make_same_mesh_connection(self._setup_actx, self.discr_from_dd(to_dd), self.discr_from_dd(from_dd)) # }}} if from_discr_tag is not DISCR_TAG_BASE: raise ValueError("cannot interpolate *from* a " "(non-interpolatory) quadrature grid") assert to_discr_tag is DISCR_TAG_BASE if from_dd.is_volume(): if to_dd.domain_tag is FACE_RESTR_ALL: return self._all_faces_volume_connection() if to_dd.domain_tag is FACE_RESTR_INTERIOR: return self._interior_faces_connection() elif to_dd.is_boundary_or_partition_interface(): assert from_discr_tag is DISCR_TAG_BASE return self._boundary_connection(to_dd.domain_tag.tag) elif to_dd.is_volume(): from meshmode.discretization.connection import \ make_same_mesh_connection to_discr = self._discr_tag_volume_discr(to_discr_tag) from_discr = self._volume_discr return make_same_mesh_connection(self._setup_actx, to_discr, from_discr) else: raise ValueError("cannot interpolate from volume to: " + str(to_dd)) else: raise ValueError("cannot interpolate from: " + str(from_dd))
def map_nodal_sum(self, expr, enclosing_prec): return DOFDesc(DTAG_SCALAR)
def map_constant(self, expr): return DOFDesc(DTAG_SCALAR)
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 dt_geometric_factors(dcoll: DiscretizationCollection, dd=None) -> DOFArray: r"""Computes a geometric scaling factor for each cell following [Hesthaven_2008]_, section 6.4, defined as the inradius (radius of an inscribed circle/sphere). Specifically, the inradius for each element is computed using the following formula from [Shewchuk_2002]_, Table 1, for simplicial cells (triangles/tetrahedra): .. math:: r_D = \frac{d V}{\sum_{i=1}^{N_{faces}} F_i}, where :math:`d` is the topological dimension, :math:`V` is the cell volume, and :math:`F_i` are the areas of each face of the cell. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization if not provided. :returns: a frozen :class:`~meshmode.dof_array.DOFArray` containing the geometric factors for each cell and at each nodal location. """ from meshmode.discretization.poly_element import SimplexElementGroupBase if dd is None: dd = DD_VOLUME actx = dcoll._setup_actx volm_discr = dcoll.discr_from_dd(dd) if any(not isinstance(grp, SimplexElementGroupBase) for grp in volm_discr.groups): raise NotImplementedError( "Geometric factors are only implemented for simplex element groups" ) if volm_discr.dim != volm_discr.ambient_dim: from warnings import warn warn( "The geometric factor for the characteristic length scale in " "time step estimation is not necessarily valid for non-volume-" "filling discretizations. Continuing anyway.", stacklevel=3) cell_vols = abs( op.elementwise_integral(dcoll, dd, volm_discr.zeros(actx) + 1.0)) if dcoll.dim == 1: return freeze(cell_vols) dd_face = DOFDesc("all_faces", dd.discretization_tag) face_discr = dcoll.discr_from_dd(dd_face) # To get a single value for the total surface area of a cell, we # take the sum over the averaged face areas on each face. # NOTE: The face areas are the *same* at each face nodal location. # This assumes there are the *same* number of face nodes on each face. surface_areas = abs( op.elementwise_integral(dcoll, dd_face, face_discr.zeros(actx) + 1.0)) surface_areas = DOFArray( actx, data=tuple( actx.einsum("fej->e", face_ae_i.reshape(vgrp.mesh_el_group.nfaces, vgrp.nelements, afgrp.nunit_dofs), tagged=(FirstAxisIsElementsTag(), )) / afgrp.nunit_dofs for vgrp, afgrp, face_ae_i in zip( volm_discr.groups, face_discr.groups, surface_areas))) return freeze( DOFArray(actx, data=tuple( actx.einsum("e,ei->ei", 1 / sae_i, cv_i, tagged=(FirstAxisIsElementsTag(), )) * dcoll.dim for cv_i, sae_i in zip(cell_vols, surface_areas))))
def euler_operator(discr, state, gas_model, boundaries, time=0.0, quadrature_tag=None): r"""Compute RHS of the Euler flow equations. Returns ------- numpy.ndarray The right-hand-side of the Euler flow equations: .. math:: \dot{\mathbf{q}} = - \nabla\cdot\mathbf{F} + (\mathbf{F}\cdot\hat{n})_{\partial\Omega} Parameters ---------- state: :class:`~mirgecom.gas_model.FluidState` Fluid state object with the conserved state, and dependent quantities. boundaries Dictionary of boundary functions, one for each valid btag time Time gas_model: :class:`~mirgecom.gas_model.GasModel` Physical gas model including equation of state, transport, and kinetic properties as required by fluid state quadrature_tag An optional identifier denoting a particular quadrature discretization to use during operator evaluations. The default value is *None*. Returns ------- :class:`mirgecom.fluid.ConservedVars` """ dd_base_vol = DOFDesc("vol") dd_quad_vol = DOFDesc("vol", quadrature_tag) dd_quad_faces = DOFDesc("all_faces", quadrature_tag) # project pair to the quadrature discretization and update dd to quad def _interp_to_surf_quad(utpair): local_dd = utpair.dd local_dd_quad = local_dd.with_discr_tag(quadrature_tag) return TracePair(local_dd_quad, interior=op.project(discr, local_dd, local_dd_quad, utpair.int), exterior=op.project(discr, local_dd, local_dd_quad, utpair.ext)) boundary_states_quad = { btag: project_fluid_state(discr, dd_base_vol, as_dofdesc(btag).with_discr_tag(quadrature_tag), state, gas_model) for btag in boundaries } # performs MPI communication of CV if needed cv_interior_pairs = [ # Get the interior trace pairs onto the surface quadrature # discretization (if any) _interp_to_surf_quad(tpair) for tpair in interior_trace_pairs(discr, state.cv) ] tseed_interior_pairs = None if state.is_mixture: # If this is a mixture, we need to exchange the temperature field because # mixture pressure (used in the inviscid flux calculations) depends on # temperature and we need to seed the temperature calculation for the # (+) part of the partition boundary with the remote temperature data. tseed_interior_pairs = [ # Get the interior trace pairs onto the surface quadrature # discretization (if any) _interp_to_surf_quad(tpair) for tpair in interior_trace_pairs(discr, state.temperature) ] interior_states_quad = make_fluid_state_trace_pairs( cv_interior_pairs, gas_model, tseed_interior_pairs) # Interpolate the fluid state to the volume quadrature grid # (this includes the conserved and dependent quantities) vol_state_quad = project_fluid_state(discr, dd_base_vol, dd_quad_vol, state, gas_model) # Compute volume contributions inviscid_flux_vol = inviscid_flux(vol_state_quad) # Compute interface contributions inviscid_flux_bnd = ( # Interior faces sum( inviscid_facial_flux(discr, state_pair) for state_pair in interior_states_quad) # Domain boundary faces + sum(boundaries[btag].inviscid_divergence_flux( discr, as_dofdesc(btag).with_discr_tag(quadrature_tag), gas_model, state_minus=boundary_states_quad[btag], time=time) for btag in boundaries)) return -div_operator(discr, dd_quad_vol, dd_quad_faces, inviscid_flux_vol, inviscid_flux_bnd)
def test_gradient(actx_factory, form, dim, order, vectorize, nested, visualize=False): actx = actx_factory() from pytools.convergence import EOCRecorder eoc_rec = EOCRecorder() for n in [4, 6, 8]: mesh = mgen.generate_regular_rect_mesh(a=(-1, ) * dim, b=(1, ) * dim, nelements_per_axis=(n, ) * dim) dcoll = DiscretizationCollection(actx, mesh, order=order) def f(x): result = dcoll.zeros(actx) + 1 for i in range(dim - 1): result = result * actx.np.sin(np.pi * x[i]) result = result * actx.np.cos(np.pi / 2 * x[dim - 1]) return result def grad_f(x): result = make_obj_array( [dcoll.zeros(actx) + 1 for _ in range(dim)]) for i in range(dim - 1): for j in range(i): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * np.pi * actx.np.cos(np.pi * x[i]) for j in range(i + 1, dim - 1): result[i] = result[i] * actx.np.sin(np.pi * x[j]) result[i] = result[i] * actx.np.cos(np.pi / 2 * x[dim - 1]) for j in range(dim - 1): result[dim - 1] = result[dim - 1] * actx.np.sin(np.pi * x[j]) result[dim - 1] = result[dim - 1] * (-np.pi / 2 * actx.np.sin(np.pi / 2 * x[dim - 1])) return result x = thaw(dcoll.nodes(), actx) if vectorize: u = make_obj_array([(i + 1) * f(x) for i in range(dim)]) else: u = f(x) def get_flux(u_tpair): dd = u_tpair.dd dd_allfaces = dd.with_dtag("all_faces") normal = thaw(dcoll.normal(dd), actx) u_avg = u_tpair.avg if vectorize: if nested: flux = make_obj_array( [u_avg_i * normal for u_avg_i in u_avg]) else: flux = np.outer(u_avg, normal) else: flux = u_avg * normal return op.project(dcoll, dd, dd_allfaces, flux) dd_allfaces = DOFDesc("all_faces") if form == "strong": grad_u = ( op.local_grad(dcoll, u, nested=nested) # No flux terms because u doesn't have inter-el jumps ) elif form == "weak": grad_u = op.inverse_mass( dcoll, -op.weak_local_grad(dcoll, u, nested=nested) # pylint: disable=E1130 + # noqa: W504 op.face_mass( dcoll, dd_allfaces, # Note: no boundary flux terms here because u_ext == u_int == 0 sum( get_flux(utpair) for utpair in op.interior_trace_pairs(dcoll, u)))) else: raise ValueError("Invalid form argument.") if vectorize: expected_grad_u = make_obj_array([(i + 1) * grad_f(x) for i in range(dim)]) if not nested: expected_grad_u = np.stack(expected_grad_u, axis=0) else: expected_grad_u = grad_f(x) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll, vis_order=order if dim == 3 else dim + 3) filename = ( f"test_gradient_{form}_{dim}_{order}" f"{'_vec' if vectorize else ''}{'_nested' if nested else ''}.vtu" ) vis.write_vtk_file(filename, [ ("u", u), ("grad_u", grad_u), ("expected_grad_u", expected_grad_u), ], overwrite=True) rel_linf_err = actx.to_numpy( op.norm(dcoll, grad_u - expected_grad_u, np.inf) / op.norm(dcoll, expected_grad_u, np.inf)) eoc_rec.add_data_point(1. / n, rel_linf_err) print("L^inf error:") print(eoc_rec) assert (eoc_rec.order_estimate() >= order - 0.5 or eoc_rec.max_error() < 1e-11)