def wave_flux(dcoll, c, w_tpair): dd = w_tpair.dd dd_quad = dd.with_discr_tag(DISCR_TAG_QUAD) u = w_tpair[0] v = w_tpair[1:] normal = thaw(u.int.array_context, op.normal(dcoll, dd)) flux_weak = flat_obj_array( np.dot(v.avg, normal), normal * u.avg, ) # upwind flux_weak += flat_obj_array( 0.5 * (u.ext - u.int), 0.5 * normal * np.dot(normal, v.ext - v.int), ) # FIXME this flux is only correct for continuous c dd_allfaces_quad = dd_quad.with_dtag("all_faces") c_quad = op.project(dcoll, "vol", dd_quad, c) flux_quad = op.project(dcoll, dd, dd_quad, flux_weak) return op.project(dcoll, dd_quad, dd_allfaces_quad, c_quad * flux_quad)
def absorbing_bc(self, w): """Construct part of the flux operator template for 1st order absorbing boundary conditions. """ actx = get_container_context_recursively(w) absorb_normal = thaw(self.dcoll.normal(dd=self.absorb_tag), actx) e, h = self.split_eh(w) if self.fixed_material: epsilon = self.epsilon mu = self.mu absorb_Z = (mu / epsilon)**0.5 # noqa: N806 absorb_Y = 1 / absorb_Z # noqa: N806 absorb_e = op.project(self.dcoll, "vol", self.absorb_tag, e) absorb_h = op.project(self.dcoll, "vol", self.absorb_tag, h) bc = flat_obj_array( absorb_e + 1 / 2 * (self.space_cross_h(absorb_normal, self.space_cross_e(absorb_normal, absorb_e)) - absorb_Z * self.space_cross_h(absorb_normal, absorb_h)), absorb_h + 1 / 2 * (self.space_cross_e(absorb_normal, self.space_cross_h(absorb_normal, absorb_h)) + absorb_Y * self.space_cross_e(absorb_normal, absorb_e))) return 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 _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 pmc_bc(self, w): """Construct part of the flux operator template for PMC boundary conditions """ e, h = self.split_eh(w) pmc_e = op.project(self.dcoll, "vol", self.pmc_tag, e) pmc_h = op.project(self.dcoll, "vol", self.pmc_tag, h) return flat_obj_array(pmc_e, -pmc_h)
def __init__(self, dcoll: DiscretizationCollection, remote_rank, vol_field, tag=None): self.tag = self.base_tag if tag is not None: self.tag += tag self.dcoll = dcoll self.array_context = vol_field.array_context self.remote_btag = BTAG_PARTITION(remote_rank) self.bdry_discr = dcoll.discr_from_dd(self.remote_btag) from grudge.op import project self.local_dof_array = project(dcoll, "vol", self.remote_btag, vol_field) local_data = self.array_context.to_numpy(flatten(self.local_dof_array)) comm = self.dcoll.mpi_communicator self.send_req = comm.Isend(local_data, remote_rank, tag=self.tag) self.remote_data_host = np.empty_like(local_data) self.recv_req = comm.Irecv(self.remote_data_host, remote_rank, self.tag)
def flux(self, u_tpair): from grudge.dof_desc import DD_VOLUME surf_v = op.project(self.dcoll, DD_VOLUME, u_tpair.dd.with_discr_tag(None), self.v) return surface_advection_weak_flux(self.dcoll, self.flux_type, u_tpair, surf_v)
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 mv_normal( actx: ArrayContext, dcoll: DiscretizationCollection, dd, ) -> MultiVector: """Exterior unit normal as a :class:`~pymbolic.geometric_algebra.MultiVector`. This supports both volume discretizations (where ambient == topological dimension) and surface discretizations (where ambient == topological dimension + 1). In the latter case, extra processing ensures that the returned normal is in the local tangent space of the element at the point where the normal is being evaluated. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. :returns: a :class:`~pymbolic.geometric_algebra.MultiVector` containing the unit normals. """ import grudge.dof_desc as dof_desc dd = dof_desc.as_dofdesc(dd) dim = dcoll.discr_from_dd(dd).dim ambient_dim = dcoll.ambient_dim if dim == ambient_dim: raise ValueError( "may only request normals on domains whose topological " f"dimension ({dim}) differs from " f"their ambient dimension ({ambient_dim})") if dim == ambient_dim - 1: return rel_mv_normal(actx, dcoll, dd=dd) # NOTE: In the case of (d - 2)-dimensional curves, we don't really have # enough information on the face to decide what an "exterior face normal" # is (e.g the "normal" to a 1D curve in 3D space is actually a # "normal plane") # # The trick done here is that we take the surface normal, move it to the # face and then take a cross product with the face tangent to get the # correct exterior face normal vector. assert dim == ambient_dim - 2 from grudge.op import project import grudge.dof_desc as dof_desc volm_normal = MultiVector( project( dcoll, dof_desc.DD_VOLUME, dd, rel_mv_normal(actx, dcoll, dd=dof_desc.DD_VOLUME).as_vector(dtype=object))) pder = pseudoscalar(actx, dcoll, dd=dd) mv = -(volm_normal ^ pder) << volm_normal.I.inv() return mv / actx.np.sqrt(mv.norm_squared())
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 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 simple_mpi_communication_entrypoint(): cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue, force_device_scalars=True) from meshmode.distributed import MPIMeshDistributor, get_partition_by_pymetis from meshmode.mesh import BTAG_ALL from mpi4py import MPI comm = MPI.COMM_WORLD num_parts = comm.Get_size() mesh_dist = MPIMeshDistributor(comm) if mesh_dist.is_mananger_rank(): from meshmode.mesh.generation import generate_regular_rect_mesh mesh = generate_regular_rect_mesh(a=(-1,)*2, b=(1,)*2, nelements_per_axis=(2,)*2) part_per_element = get_partition_by_pymetis(mesh, num_parts) local_mesh = mesh_dist.send_mesh_parts(mesh, part_per_element, num_parts) else: local_mesh = mesh_dist.receive_mesh_part() dcoll = DiscretizationCollection(actx, local_mesh, order=5, mpi_communicator=comm) x = thaw(dcoll.nodes(), actx) myfunc = actx.np.sin(np.dot(x, [2, 3])) from grudge.dof_desc import as_dofdesc dd_int = as_dofdesc("int_faces") dd_vol = as_dofdesc("vol") dd_af = as_dofdesc("all_faces") all_faces_func = op.project(dcoll, dd_vol, dd_af, myfunc) int_faces_func = op.project(dcoll, dd_vol, dd_int, myfunc) bdry_faces_func = op.project(dcoll, BTAG_ALL, dd_af, op.project(dcoll, dd_vol, BTAG_ALL, myfunc)) hopefully_zero = ( op.project( dcoll, "int_faces", "all_faces", dcoll.opposite_face_connection()(int_faces_func) ) + sum(op.project(dcoll, tpair.dd, "all_faces", tpair.int) for tpair in op.cross_rank_trace_pairs(dcoll, myfunc)) ) - (all_faces_func - bdry_faces_func) error = actx.to_numpy(flat_norm(hopefully_zero, ord=np.inf)) print(__file__) with np.printoptions(threshold=100000000, suppress=True): logger.debug(hopefully_zero) logger.info("error: %.5e", error) assert error < 1e-14
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)
def operator(self, t, w): dcoll = self.dcoll u = w[0] v = w[1:] actx = u.array_context # boundary conditions ------------------------------------------------- # dirichlet BCs ------------------------------------------------------- dir_u = op.project(dcoll, "vol", self.dirichlet_tag, u) dir_v = op.project(dcoll, "vol", self.dirichlet_tag, v) if self.dirichlet_bc_f: # FIXME from warnings import warn warn("Inhomogeneous Dirichlet conditions on the wave equation " "are still having issues.") dir_g = self.dirichlet_bc_f dir_bc = flat_obj_array(2 * dir_g - dir_u, dir_v) else: dir_bc = flat_obj_array(-dir_u, dir_v) # neumann BCs --------------------------------------------------------- neu_u = op.project(dcoll, "vol", self.neumann_tag, u) neu_v = op.project(dcoll, "vol", self.neumann_tag, v) neu_bc = flat_obj_array(neu_u, -neu_v) # radiation BCs ------------------------------------------------------- rad_normal = thaw(dcoll.normal(dd=self.radiation_tag), actx) rad_u = op.project(dcoll, "vol", self.radiation_tag, u) rad_v = op.project(dcoll, "vol", self.radiation_tag, v) rad_bc = flat_obj_array( 0.5 * (rad_u - self.sign * np.dot(rad_normal, rad_v)), 0.5 * rad_normal * (np.dot(rad_normal, rad_v) - self.sign * rad_u)) # entire operator ----------------------------------------------------- def flux(tpair): return op.project(dcoll, tpair.dd, "all_faces", self.flux(tpair)) result = (op.inverse_mass( dcoll, flat_obj_array(-self.c * op.weak_local_div(dcoll, v), -self.c * op.weak_local_grad(dcoll, u)) - op.face_mass( dcoll, sum( flux(tpair) for tpair in op.interior_trace_pairs(dcoll, w)) + flux(op.bv_trace_pair(dcoll, self.dirichlet_tag, w, dir_bc)) + flux(op.bv_trace_pair(dcoll, self.neumann_tag, w, neu_bc)) + flux(op.bv_trace_pair(dcoll, self.radiation_tag, w, rad_bc))))) result[0] = result[0] + self.source_f(actx, dcoll, t) return result
def wave_flux(dcoll, c, w_tpair): u = w_tpair.u v = w_tpair.v normal = thaw(dcoll.normal(w_tpair.dd), u.int.array_context) flux_weak = WaveState(u=v.avg @ normal, v=u.avg * normal) # upwind v_jump = v.diff @ normal flux_weak += WaveState( u=0.5 * u.diff, v=0.5 * v_jump * normal, ) return op.project(dcoll, w_tpair.dd, "all_faces", c * flux_weak)
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 wave_flux(dcoll, c, w_tpair): u = w_tpair[0] v = w_tpair[1:] normal = thaw(u.int.array_context, op.normal(dcoll, w_tpair.dd)) flux_weak = flat_obj_array( np.dot(v.avg, normal), normal * u.avg, ) # upwind flux_weak += flat_obj_array( 0.5 * (u.ext - u.int), 0.5 * normal * np.dot(normal, v.ext - v.int), ) return op.project(dcoll, w_tpair.dd, "all_faces", c * flux_weak)
def _normal(): dim = dcoll.discr_from_dd(dd).dim ambient_dim = dcoll.ambient_dim if dim == ambient_dim: raise ValueError( "may only request normals on domains whose topological " f"dimension ({dim}) differs from " f"their ambient dimension ({ambient_dim})") if dim == ambient_dim - 1: result = rel_mv_normal(actx, dcoll, dd=dd) else: # NOTE: In the case of (d - 2)-dimensional curves, we don't really have # enough information on the face to decide what an "exterior face normal" # is (e.g the "normal" to a 1D curve in 3D space is actually a # "normal plane") # # The trick done here is that we take the surface normal, move it to the # face and then take a cross product with the face tangent to get the # correct exterior face normal vector. assert dim == ambient_dim - 2 from grudge.op import project volm_normal = MultiVector( project( dcoll, dof_desc.DD_VOLUME, dd, rel_mv_normal( actx, dcoll, dd=dof_desc.DD_VOLUME).as_vector(dtype=object))) pder = pseudoscalar(actx, dcoll, dd=dd) mv = -(volm_normal ^ pder) << volm_normal.I.inv() result = mv / actx.np.sqrt(mv.norm_squared()) if _use_geoderiv_connection: result = dcoll._base_to_geoderiv_connection(dd)(result) return freeze(result, actx)
def _interior_trace_pair(dcoll: DiscretizationCollection, vec) -> TracePair: r"""Return a :class:`TracePair` for the interior faces of *dcoll* with a discretization tag specified by *discr_tag*. This does not include interior faces on different MPI ranks. :arg vec: a :class:`~meshmode.dof_array.DOFArray` or object array of :class:`~meshmode.dof_array.DOFArray`\ s. :returns: a :class:`TracePair` object. """ from grudge.op import project 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 bv_trace_pair(dcoll: DiscretizationCollection, dd, interior, exterior) -> TracePair: """Returns a trace pair defined on the exterior boundary. The interior argument is assumed to be defined on the volume discretization, and will therefore be restricted to the boundary *dd* prior to creating a :class:`TracePair`. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one, which describes the boundary discretization. :arg interior: a :class:`~meshmode.dof_array.DOFArray` that contains data defined in the volume, which will be restricted to the boundary denoted by *dd*. The result will be used as the interior value for the flux. :arg exterior: a :class:`~meshmode.dof_array.DOFArray` that contains data that already lives on the boundary representing the exterior value to be used for the flux. :returns: a :class:`TracePair` on the boundary. """ from grudge.op import project interior = project(dcoll, "vol", dd, interior) return bdry_trace_pair(dcoll, dd, interior, exterior)
def test_2d_gauss_theorem(actx_factory): """Verify Gauss's theorem explicitly on a mesh""" pytest.importorskip("meshpy") from meshpy.geometry import make_circle, GeometryBuilder from meshpy.triangle import MeshInfo, build geob = GeometryBuilder() geob.add_geometry(*make_circle(1)) mesh_info = MeshInfo() geob.set(mesh_info) mesh_info = build(mesh_info) from meshmode.mesh.io import from_meshpy from meshmode.mesh import BTAG_ALL mesh = from_meshpy(mesh_info, order=1) actx = actx_factory() dcoll = DiscretizationCollection(actx, mesh, order=2) volm_disc = dcoll.discr_from_dd(dof_desc.DD_VOLUME) x_volm = thaw(volm_disc.nodes(), actx) def f(x): return flat_obj_array( actx.np.sin(3 * x[0]) + actx.np.cos(3 * x[1]), actx.np.sin(2 * x[0]) + actx.np.cos(x[1])) f_volm = f(x_volm) int_1 = op.integral(dcoll, "vol", op.local_div(dcoll, f_volm)) prj_f = op.project(dcoll, "vol", BTAG_ALL, f_volm) normal = thaw(dcoll.normal(BTAG_ALL), actx) int_2 = op.integral(dcoll, BTAG_ALL, prj_f.dot(normal)) assert abs(int_1 - int_2) < 1e-13
def wave_operator(dcoll, c, w): u = w.u v = w.v dir_w = op.project(dcoll, "vol", BTAG_ALL, w) dir_u = dir_w.u dir_v = dir_w.v dir_bval = WaveState(u=dir_u, v=dir_v) dir_bc = WaveState(u=-dir_u, v=dir_v) return (op.inverse_mass( dcoll, WaveState(u=-c * op.weak_local_div(dcoll, v), v=-c * op.weak_local_grad(dcoll, u)) + op.face_mass( dcoll, 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 test_face_normal_surface(actx_factory, mesh_name): """Check that face normals are orthogonal to the surface normal""" actx = actx_factory() # {{{ geometry if mesh_name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif mesh_name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() else: raise ValueError("unknown mesh name: %s" % mesh_name) mesh = builder.get_mesh(builder.resolutions[0], builder.mesh_order) dcoll = DiscretizationCollection(actx, mesh, order=builder.order) volume_discr = dcoll.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume_discr.ndofs) logger.info("nelements: %d", volume_discr.mesh.nelements) # }}} # {{{ Compute surface and face normals from meshmode.discretization.connection import FACE_RESTR_INTERIOR from grudge.geometry import normal dv = dof_desc.DD_VOLUME df = dof_desc.as_dofdesc(FACE_RESTR_INTERIOR) ambient_dim = mesh.ambient_dim surf_normal = op.project(dcoll, dv, df, normal(actx, dcoll, dd=dv)) surf_normal = surf_normal / actx.np.sqrt(sum(surf_normal**2)) face_normal_i = thaw(dcoll.normal(df), actx) face_normal_e = dcoll.opposite_face_connection()(face_normal_i) if mesh.ambient_dim == 3: from grudge.geometry import pseudoscalar, area_element # NOTE: there's only one face tangent in 3d face_tangent = (pseudoscalar(actx, dcoll, dd=df) / area_element(actx, dcoll, dd=df)).as_vector( dtype=object) # }}} # {{{ checks def _eval_error(x): return op.norm(dcoll, x, np.inf, dd=df) rtol = 1.0e-14 # check interpolated surface normal is orthogonal to face normal error = _eval_error(surf_normal.dot(face_normal_i)) logger.info("error[n_dot_i]: %.5e", error) assert error < rtol # check angle between two neighboring elements error = _eval_error(face_normal_i.dot(face_normal_e) + 1.0) logger.info("error[i_dot_e]: %.5e", error) assert error > rtol # check orthogonality with face tangent if ambient_dim == 3: error = _eval_error(face_tangent.dot(face_normal_i)) logger.info("error[t_dot_i]: %.5e", error) assert error < 5 * rtol
dt = 0.001 t = 0 t_final = 0.5 # timestepper loop while t < t_final: # extract the left boundary trace pair lbnd_tpair = op.bv_trace_pair(dcoll, dd=left_bndry, interior=uh, exterior=left_boundary_condition(x_bndry, t)) # extract the right boundary trace pair rbnd_tpair = op.bv_trace_pair(dcoll, dd=right_bndry, interior=uh, exterior=op.project(dcoll, "vol", right_bndry, uh)) # extract the trace pairs on the interior faces interior_tpair = op.interior_trace_pair(dcoll, uh) Su = op.weak_local_grad(dcoll, uh) lift = op.face_mass(dcoll, # left boundary weak-flux terms op.project(dcoll, left_bndry, "all_faces", flux(dcoll, lbnd_tpair)) # right boundary weak-flux terms + op.project(dcoll, right_bndry, "all_faces", flux(dcoll, rbnd_tpair)) # interior weak-flux terms
def to_quad(arg): return op.project(dcoll, DD_VOLUME, quad_dd, arg)
def flux(pair): return op.project(dcoll, pair.dd, "all_faces", self.flux(pair))
def flux(self, u_tpair): from grudge.dof_desc import DD_VOLUME surf_v = op.project(self.dcoll, DD_VOLUME, u_tpair.dd, self.v) return advection_weak_flux(self.dcoll, self.flux_type, u_tpair, surf_v)
def test_surface_divergence_theorem(actx_factory, mesh_name, visualize=False): r"""Check the surface divergence theorem. .. math:: \int_Sigma \phi \nabla_i f_i = \int_\Sigma \nabla_i \phi f_i + \int_\Sigma \kappa \phi f_i n_i + \int_{\partial \Sigma} \phi f_i m_i where :math:`n_i` is the surface normal and :class:`m_i` is the face normal (which should be orthogonal to both the surface normal and the face tangent). """ actx = actx_factory() # {{{ cases if mesh_name == "2-1-ellipse": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=3.1, aspect_ratio=2.0) elif mesh_name == "spheroid": from mesh_data import SpheroidMeshBuilder builder = SpheroidMeshBuilder() elif mesh_name == "circle": from mesh_data import EllipseMeshBuilder builder = EllipseMeshBuilder(radius=1.0, aspect_ratio=1.0) elif mesh_name == "starfish": from mesh_data import StarfishMeshBuilder builder = StarfishMeshBuilder() elif mesh_name == "sphere": from mesh_data import SphereMeshBuilder builder = SphereMeshBuilder(radius=1.0, mesh_order=16) else: raise ValueError("unknown mesh name: %s" % mesh_name) # }}} # {{{ convergence def f(x): return flat_obj_array( actx.np.sin(3 * x[1]) + actx.np.cos(3 * x[0]) + 1.0, actx.np.sin(2 * x[0]) + actx.np.cos(x[1]), 3.0 * actx.np.cos(x[0] / 2) + actx.np.cos(x[1]), )[:ambient_dim] from pytools.convergence import EOCRecorder eoc_global = EOCRecorder() eoc_local = EOCRecorder() theta = np.pi / 3.33 ambient_dim = builder.ambient_dim if ambient_dim == 2: mesh_rotation = np.array([ [np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)], ]) else: mesh_rotation = np.array([ [1.0, 0.0, 0.0], [0.0, np.cos(theta), -np.sin(theta)], [0.0, np.sin(theta), np.cos(theta)], ]) mesh_offset = np.array([0.33, -0.21, 0.0])[:ambient_dim] for i, resolution in enumerate(builder.resolutions): from meshmode.mesh.processing import affine_map from meshmode.discretization.connection import FACE_RESTR_ALL mesh = builder.get_mesh(resolution, builder.mesh_order) mesh = affine_map(mesh, A=mesh_rotation, b=mesh_offset) from meshmode.discretization.poly_element import \ QuadratureSimplexGroupFactory qtag = dof_desc.DISCR_TAG_QUAD dcoll = DiscretizationCollection(actx, mesh, order=builder.order, discr_tag_to_group_factory={ qtag: QuadratureSimplexGroupFactory( 2 * builder.order) }) volume = dcoll.discr_from_dd(dof_desc.DD_VOLUME) logger.info("ndofs: %d", volume.ndofs) logger.info("nelements: %d", volume.mesh.nelements) dd = dof_desc.DD_VOLUME dq = dd.with_discr_tag(qtag) df = dof_desc.as_dofdesc(FACE_RESTR_ALL) ambient_dim = dcoll.ambient_dim # variables f_num = f(thaw(dcoll.nodes(dd=dd), actx)) f_quad_num = f(thaw(dcoll.nodes(dd=dq), actx)) from grudge.geometry import normal, summed_curvature kappa = summed_curvature(actx, dcoll, dd=dq) normal = normal(actx, dcoll, dd=dq) face_normal = thaw(dcoll.normal(df), actx) face_f = op.project(dcoll, dd, df, f_num) # operators stiff = op.mass( dcoll, sum( op.local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num))) stiff_t = sum( op.weak_local_d_dx(dcoll, i, f_num_i) for i, f_num_i in enumerate(f_num)) kterm = op.mass(dcoll, dq, kappa * f_quad_num.dot(normal)) flux = op.face_mass(dcoll, face_f.dot(face_normal)) # sum everything up op_global = op.nodal_sum(dcoll, dd, stiff - (stiff_t + kterm)) op_local = op.elementwise_sum(dcoll, dd, stiff - (stiff_t + kterm + flux)) err_global = abs(op_global) err_local = op.norm(dcoll, op_local, np.inf) logger.info("errors: global %.5e local %.5e", err_global, err_local) # compute max element size from grudge.dt_utils import h_max_from_volume h_max = h_max_from_volume(dcoll) eoc_global.add_data_point(h_max, actx.to_numpy(err_global)) eoc_local.add_data_point(h_max, err_local) if visualize: from grudge.shortcuts import make_visualizer vis = make_visualizer(dcoll) filename = f"surface_divergence_theorem_{mesh_name}_{i:04d}.vtu" vis.write_vtk_file(filename, [("r", actx.np.log10(op_local))], overwrite=True) # }}} order = min(builder.order, builder.mesh_order) - 0.5 logger.info("\n%s", str(eoc_global)) logger.info("\n%s", str(eoc_local)) assert eoc_global.max_error() < 1.0e-12 \ or eoc_global.order_estimate() > order - 0.5 assert eoc_local.max_error() < 1.0e-12 \ or eoc_local.order_estimate() > order - 0.5
def project(self, src, tgt, vec): return op.project(self, src, tgt, vec)
def flux(tpair): return op.project(dcoll, tpair.dd, face_dd, self.flux(tpair))