def test_parallel_vtk_file(actx_factory, dim): r""" Simple test just generates a sample parallel PVTU file and checks it against the expected result. The expected result is just a file in the tests directory. """ logging.basicConfig(level=logging.INFO) actx = actx_factory() nelements = 64 target_order = 4 if dim == 1: mesh = mgen.make_curve_mesh(mgen.NArmedStarfish(5, 0.25), np.linspace(0.0, 1.0, nelements + 1), target_order) elif dim == 2: mesh = mgen.generate_torus(5.0, 1.0, order=target_order) elif dim == 3: mesh = mgen.generate_warped_rect_mesh(dim, target_order, nelements_side=4) else: raise ValueError("unknown dimensionality") from meshmode.discretization import Discretization discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from meshmode.discretization.visualization import make_visualizer vis = make_visualizer(actx, discr, target_order) class FakeComm: def Get_rank(self): # noqa: N802 return 0 def Get_size(self): # noqa: N802 return 2 file_name_pattern = f"visualizer_vtk_linear_{dim}_{{rank}}.vtu" pvtu_filename = file_name_pattern.format(rank=0).replace("vtu", "pvtu") vis.write_parallel_vtk_file( FakeComm(), file_name_pattern, [("scalar", discr.zeros(actx)), ("vector", make_obj_array([discr.zeros(actx) for i in range(dim)]))], overwrite=True) import os assert os.path.exists(pvtu_filename) import filecmp assert filecmp.cmp(f"ref-{pvtu_filename}", pvtu_filename)
def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) nelements = 300 target_order = 8 fmm_order = 4 # {{{ geometry mesh = make_curve_mesh(WobblyCircle.random(8, seed=30), np.linspace(0, 1, nelements+1), target_order) from pytential.unregularized import UnregularizedLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) direct = UnregularizedLayerPotentialSource( density_discr, fmm_order=False, ) fmm = direct.copy( fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order) sigma = density_discr.zeros(actx) + 1 fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100) from pytential.target import PointsTarget ptarget = PointsTarget(fplot.points) from pytential import GeometryCollection places = GeometryCollection({ "unregularized_direct": direct, "unregularized_fmm": fmm, "targets": ptarget}) # }}} # {{{ check from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None) direct_fld_in_vol = bind(places, op, auto_where=("unregularized_direct", "targets"))( actx, sigma=sigma) fmm_fld_in_vol = bind(places, op, auto_where=("unregularized_fmm", "targets"))(actx, sigma=sigma) err = actx.np.fabs(fmm_fld_in_vol - direct_fld_in_vol) linf_err = actx.to_numpy(err).max() print("l_inf error:", linf_err) assert linf_err < 5e-3
def test_unregularized_with_ones_kernel(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) nelements = 10 order = 8 mesh = make_curve_mesh(partial(ellipse, 1), np.linspace(0, 1, nelements+1), order) from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(order)) from pytential.unregularized import UnregularizedLayerPotentialSource lpot_source = UnregularizedLayerPotentialSource(discr) from pytential.target import PointsTarget targets = PointsTarget(np.zeros((2, 1), dtype=float)) places = GeometryCollection({ sym.DEFAULT_SOURCE: lpot_source, sym.DEFAULT_TARGET: lpot_source, "target_non_self": targets}) from sumpy.kernel import one_kernel_2d sigma_sym = sym.var("sigma") op = sym.IntG(one_kernel_2d, sigma_sym, qbx_forced_limit=None) sigma = discr.zeros(actx) + 1 result_self = bind(places, op, auto_where=places.auto_where)( actx, sigma=sigma) result_nonself = bind(places, op, auto_where=(places.auto_source, "target_non_self"))( actx, sigma=sigma) from meshmode.dof_array import flatten assert np.allclose(actx.to_numpy(flatten(result_self)), 2 * np.pi) assert np.allclose(actx.to_numpy(result_nonself), 2 * np.pi)
def test_unregularized_off_surface_fmm_vs_direct(ctx_getter): cl_ctx = ctx_getter() queue = cl.CommandQueue(cl_ctx) nelements = 300 target_order = 8 fmm_order = 4 mesh = make_curve_mesh(WobblyCircle.random(8, seed=30), np.linspace(0, 1, nelements+1), target_order) from pytential.unregularized import UnregularizedLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) direct = UnregularizedLayerPotentialSource( density_discr, fmm_order=False, ) fmm = direct.copy( fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order) sigma = density_discr.zeros(queue) + 1 fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100) from pytential.target import PointsTarget ptarget = PointsTarget(fplot.points) from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None) direct_fld_in_vol = bind((direct, ptarget), op)(queue, sigma=sigma) fmm_fld_in_vol = bind((fmm, ptarget), op)(queue, sigma=sigma) err = cl.clmath.fabs(fmm_fld_in_vol - direct_fld_in_vol) linf_err = cl.array.max(err).get() print("l_inf error:", linf_err) assert linf_err < 5e-3
def test_unregularized_off_surface_fmm_vs_direct(ctx_factory): cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) nelements = 300 target_order = 8 fmm_order = 4 mesh = make_curve_mesh(WobblyCircle.random(8, seed=30), np.linspace(0, 1, nelements + 1), target_order) from pytential.unregularized import UnregularizedLayerPotentialSource from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) direct = UnregularizedLayerPotentialSource( density_discr, fmm_order=False, ) fmm = direct.copy( fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order) sigma = density_discr.zeros(queue) + 1 fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100) from pytential.target import PointsTarget ptarget = PointsTarget(fplot.points) from sumpy.kernel import LaplaceKernel op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None) direct_fld_in_vol = bind((direct, ptarget), op)(queue, sigma=sigma) fmm_fld_in_vol = bind((fmm, ptarget), op)(queue, sigma=sigma) err = cl.clmath.fabs(fmm_fld_in_vol - direct_fld_in_vol) linf_err = cl.array.max(err).get() print("l_inf error:", linf_err) assert linf_err < 5e-3
def test_to_fd_idempotency(ctx_factory, mm_mesh, fspace_degree): """ Make sure mm->fd->mm and (mm->)->fd->mm->fd are identity """ cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) # make sure degree is higher order than mesh fspace_degree += mm_mesh.groups[0].order # Make a function space and a function with unique values at each node factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) fdrake_connection = build_connection_to_firedrake(discr) fdrake_mesh = fdrake_connection.firedrake_fspace().mesh() dtype = fdrake_mesh.coordinates.dat.data.dtype mm_unique = discr.zeros(actx, dtype=dtype) unique_vals = np.arange(np.size(mm_unique[0]), dtype=dtype) mm_unique[0].set(unique_vals.reshape(mm_unique[0].shape)) mm_unique_copy = DOFArray(actx, (mm_unique[0].copy(), )) # Test for idempotency mm->fd->mm fdrake_unique = fdrake_connection.from_meshmode(mm_unique) fdrake_connection.from_firedrake(fdrake_unique, out=mm_unique_copy) np.testing.assert_allclose(actx.to_numpy(mm_unique_copy[0]), actx.to_numpy(mm_unique[0]), atol=CLOSE_ATOL) # Test for idempotency (mm->)fd->mm->fd fdrake_unique_copy = fdrake_connection.from_meshmode(mm_unique_copy) np.testing.assert_allclose(fdrake_unique_copy.dat.data, fdrake_unique.dat.data, atol=CLOSE_ATOL)
class DGDiscretization: def __init__(self, actx, mesh, order): self.order = order from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory self.group_factory = PolynomialWarpAndBlendGroupFactory(order=order) self.volume_discr = Discretization(actx, mesh, self.group_factory) assert self.volume_discr.dim == 2 @property def _setup_actx(self): return self.volume_discr._setup_actx @property def array_context(self): return self.volume_discr.array_context @property def dim(self): return self.volume_discr.dim # {{{ discretizations/connections @memoize_method def boundary_connection(self, boundary_tag): from meshmode.discretization.connection import make_face_restriction return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, boundary_tag=boundary_tag) @memoize_method def interior_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_INTERIOR) return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_INTERIOR, per_face_groups=False) @memoize_method def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self._setup_actx, self.interior_faces_connection()) @memoize_method def all_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_ALL) return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_ALL, per_face_groups=False) @memoize_method def get_to_all_face_embedding(self, where): from meshmode.discretization.connection import \ make_face_to_all_faces_embedding faces_conn = self.get_connection("vol", where) return make_face_to_all_faces_embedding(self._setup_actx, faces_conn, self.get_discr("all_faces")) def get_connection(self, src, tgt): src_tgt = (src, tgt) if src_tgt == ("vol", "int_faces"): return self.interior_faces_connection() elif src_tgt == ("vol", "all_faces"): return self.all_faces_connection() elif src_tgt == ("vol", BTAG_ALL): return self.boundary_connection(tgt) elif src_tgt == ("int_faces", "all_faces"): return self.get_to_all_face_embedding(src) elif src_tgt == (BTAG_ALL, "all_faces"): return self.get_to_all_face_embedding(src) else: raise ValueError(f"locations '{src}'->'{tgt}' not understood") def interp(self, src, tgt, vec): if (isinstance(vec, np.ndarray) and vec.dtype.char == "O" and not isinstance(vec, DOFArray)): return obj_array_vectorize(lambda el: self.interp(src, tgt, el), vec) return self.get_connection(src, tgt)(vec) def get_discr(self, where): if where == "vol": return self.volume_discr elif where == "all_faces": return self.all_faces_connection().to_discr elif where == "int_faces": return self.interior_faces_connection().to_discr elif where == BTAG_ALL: return self.boundary_connection(where).to_discr else: raise ValueError(f"location '{where}' not understood") # }}} @memoize_method def parametrization_derivative(self): return freeze( parametrization_derivative(self._setup_actx, self.volume_discr)) @memoize_method def vol_jacobian(self): [a, b], [c, d] = thaw(self._setup_actx, self.parametrization_derivative()) return freeze(a * d - b * c) @memoize_method def inverse_parametrization_derivative(self): [a, b], [c, d] = thaw(self._setup_actx, self.parametrization_derivative()) result = np.zeros((2, 2), dtype=object) det = a * d - b * c result[0, 0] = d / det result[0, 1] = -b / det result[1, 0] = -c / det result[1, 1] = a / det return freeze(result) def zeros(self, actx): return self.volume_discr.zeros(actx) def grad(self, vec): ipder = self.inverse_parametrization_derivative() dref = [ self.volume_discr.num_reference_derivative((idim, ), vec) for idim in range(self.volume_discr.dim) ] return make_obj_array([ sum(dref_i * ipder_i for dref_i, ipder_i in zip(dref, ipder[iambient])) for iambient in range(self.volume_discr.ambient_dim) ]) def div(self, vecs): return sum(self.grad(vec_i)[i] for i, vec_i in enumerate(vecs)) @memoize_method def normal(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) nrm = 1 / (a**2 + b**2)**0.5 return freeze(flat_obj_array(b * nrm, -a * nrm)) @memoize_method def face_jacobian(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) return freeze((a**2 + b**2)**0.5) @memoize_method def get_inverse_mass_matrix(self, grp, dtype): import modepy as mp matrix = mp.inverse_mass_matrix(grp.basis(), grp.unit_nodes) actx = self._setup_actx return actx.freeze(actx.from_numpy(matrix)) def inverse_mass(self, vec): if (isinstance(vec, np.ndarray) and vec.dtype.char == "O" and not isinstance(vec, DOFArray)): return obj_array_vectorize(lambda el: self.inverse_mass(el), vec) @memoize_in(self, "elwise_linear_knl") def knl(): return make_loopy_program( """{[iel,idof,j]: 0<=iel<nelements and 0<=idof<ndiscr_nodes_out and 0<=j<ndiscr_nodes_in}""", "result[iel,idof] = sum(j, mat[idof, j] * vec[iel, j])", name="diff") discr = self.volume_discr result = discr.empty_like(vec) for grp in discr.groups: matrix = self.get_inverse_mass_matrix(grp, vec.entry_dtype) vec.array_context.call_loopy(knl(), mat=matrix, result=result[grp.index], vec=vec[grp.index]) return result / self.vol_jacobian() @memoize_method def get_local_face_mass_matrix(self, afgrp, volgrp, dtype): nfaces = volgrp.mesh_el_group.nfaces assert afgrp.nelements == nfaces * volgrp.nelements matrix = np.empty((volgrp.nunit_dofs, nfaces, afgrp.nunit_dofs), dtype=dtype) from modepy.tools import UNIT_VERTICES import modepy as mp for iface, fvi in enumerate( volgrp.mesh_el_group.face_vertex_indices()): face_vertices = UNIT_VERTICES[volgrp.dim][np.array(fvi)].T matrix[:, iface, :] = mp.nodal_face_mass_matrix( volgrp.basis(), volgrp.unit_nodes, afgrp.unit_nodes, volgrp.order, face_vertices) actx = self._setup_actx return actx.freeze(actx.from_numpy(matrix)) def face_mass(self, vec): if (isinstance(vec, np.ndarray) and vec.dtype.char == "O" and not isinstance(vec, DOFArray)): return obj_array_vectorize(lambda el: self.face_mass(el), vec) @memoize_in(self, "face_mass_knl") def knl(): return make_loopy_program( """{[iel,idof,f,j]: 0<=iel<nelements and 0<=f<nfaces and 0<=idof<nvol_nodes and 0<=j<nface_nodes}""", "result[iel,idof] = " "sum(f, sum(j, mat[idof, f, j] * vec[f, iel, j]))", name="face_mass") all_faces_conn = self.get_connection("vol", "all_faces") all_faces_discr = all_faces_conn.to_discr vol_discr = all_faces_conn.from_discr result = vol_discr.empty_like(vec) fj = self.face_jacobian("all_faces") vec = vec * fj assert len(all_faces_discr.groups) == len(vol_discr.groups) for afgrp, volgrp in zip(all_faces_discr.groups, vol_discr.groups): nfaces = volgrp.mesh_el_group.nfaces matrix = self.get_local_face_mass_matrix(afgrp, volgrp, vec.entry_dtype) vec.array_context.call_loopy(knl(), mat=matrix, result=result[volgrp.index], vec=vec[afgrp.index].reshape( nfaces, volgrp.nelements, afgrp.nunit_dofs)) return result
class DiscretizationCollection: """A collection of discretizations, defined on the same underlying :class:`~meshmode.mesh.Mesh`, corresponding to various mesh entities (volume, interior facets, boundaries) and associated element groups. .. automethod:: __init__ .. autoattribute:: dim .. autoattribute:: ambient_dim .. autoattribute:: mesh .. autoattribute:: real_dtype .. autoattribute:: complex_dtype .. automethod:: discr_from_dd .. automethod:: connection_from_dds .. automethod:: empty .. automethod:: zeros .. automethod:: nodes .. automethod:: normal .. rubric:: Internal functionality .. automethod:: _base_to_geoderiv_connection """ # {{{ constructor def __init__( self, array_context: ArrayContext, mesh: Mesh, order=None, discr_tag_to_group_factory=None, mpi_communicator=None, # FIXME: `quad_tag_to_group_factory` is deprecated quad_tag_to_group_factory=None): """ :arg discr_tag_to_group_factory: A mapping from discretization tags (typically one of: :class:`grudge.dof_desc.DISCR_TAG_BASE`, :class:`grudge.dof_desc.DISCR_TAG_MODAL`, or :class:`grudge.dof_desc.DISCR_TAG_QUAD`) to a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` indicating with which type of discretization the operations are to be carried out, or *None* to indicate that operations with this discretization tag should be carried out with the standard volume discretization. """ if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is not None): raise ValueError( "Both `quad_tag_to_group_factory` and `discr_tag_to_group_factory` " "are specified. Use `discr_tag_to_group_factory` instead.") # FIXME: `quad_tag_to_group_factory` is deprecated if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is None): warn( "`quad_tag_to_group_factory` is a deprecated kwarg and will " "be dropped in version 2022.x. Use `discr_tag_to_group_factory` " "instead.", DeprecationWarning, stacklevel=2) discr_tag_to_group_factory = quad_tag_to_group_factory self._setup_actx = array_context.clone() from meshmode.discretization.poly_element import \ default_simplex_group_factory if discr_tag_to_group_factory is None: if order is None: raise TypeError( "one of 'order' and 'discr_tag_to_group_factory' must be given" ) discr_tag_to_group_factory = { DISCR_TAG_BASE: default_simplex_group_factory(base_dim=mesh.dim, order=order) } else: if order is not None: discr_tag_to_group_factory = discr_tag_to_group_factory.copy() if DISCR_TAG_BASE in discr_tag_to_group_factory: raise ValueError( "if 'order' is given, 'discr_tag_to_group_factory' must " "not have a key of DISCR_TAG_BASE") discr_tag_to_group_factory[DISCR_TAG_BASE] = \ default_simplex_group_factory(base_dim=mesh.dim, order=order) # Modal discr should always come from the base discretization discr_tag_to_group_factory[DISCR_TAG_MODAL] = \ _generate_modal_group_factory( discr_tag_to_group_factory[DISCR_TAG_BASE] ) self.discr_tag_to_group_factory = discr_tag_to_group_factory from meshmode.discretization import Discretization self._volume_discr = Discretization( array_context, mesh, self.group_factory_for_discretization_tag(DISCR_TAG_BASE)) # NOTE: Can be removed when symbolics are completely removed # {{{ management of discretization-scoped common subexpressions from pytools import UniqueNameGenerator self._discr_scoped_name_gen = UniqueNameGenerator() self._discr_scoped_subexpr_to_name = {} self._discr_scoped_subexpr_name_to_value = {} # }}} self._dist_boundary_connections = \ self._set_up_distributed_communication( mpi_communicator, array_context) self.mpi_communicator = mpi_communicator # }}} @property def quad_tag_to_group_factory(self): warn( "`DiscretizationCollection.quad_tag_to_group_factory` " "is deprecated and will go away in 2022. Use " "`DiscretizationCollection.discr_tag_to_group_factory` " "instead.", DeprecationWarning, stacklevel=2) return self.discr_tag_to_group_factory def get_management_rank_index(self): return 0 def is_management_rank(self): if self.mpi_communicator is None: return True else: return self.mpi_communicator.Get_rank() \ == self.get_management_rank_index() # {{{ distributed 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 get_distributed_boundary_swap_connection(self, dd): warn( "`DiscretizationCollection.get_distributed_boundary_swap_connection` " "is deprecated and will go away in 2022. Use " "`DiscretizationCollection.distributed_boundary_swap_connection` " "instead.", DeprecationWarning, stacklevel=2) return self.distributed_boundary_swap_connection(dd) def distributed_boundary_swap_connection(self, dd): """Provides a mapping from the base volume discretization to the exterior boundary restriction on a parallel boundary partition described by *dd*. This connection is used to communicate across element boundaries in different parallel partitions during distributed runs. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. The domain tag must be a subclass of :class:`grudge.dof_desc.DTAG_BOUNDARY` with an associated :class:`meshmode.mesh.BTAG_PARTITION` corresponding to a particular communication rank. """ if dd.discretization_tag is not DISCR_TAG_BASE: # FIXME raise NotImplementedError( "Distributed communication with discretization tag " f"{dd.discretization_tag} is not implemented.") assert isinstance(dd.domain_tag, DTAG_BOUNDARY) assert isinstance(dd.domain_tag.tag, BTAG_PARTITION) return self._dist_boundary_connections[dd.domain_tag.tag.part_nr] # }}} # {{{ discr_from_dd @memoize_method def discr_from_dd(self, dd): """Provides a :class:`meshmode.discretization.Discretization` object from *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. """ 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)) # }}} # {{{ _base_to_geoderiv_connection @memoize_method def _has_affine_groups(self): from modepy.shapes import Simplex return any( megrp.is_affine and issubclass(megrp._modepy_shape_cls, Simplex) for megrp in self._volume_discr.mesh.groups) @memoize_method def _base_to_geoderiv_connection(self, dd: DOFDesc): r"""The "geometry derivatives" discretization for a given *dd* is typically identical to the one returned by :meth:`discr_from_dd`, however for affinely-mapped simplicial elements, it will use a :math:`P^0` discretization having a single DOF per element. As a result, :class:`~meshmode.dof_array.DOFArray`\ s on this are broadcast-compatible with the discretizations returned by :meth:`discr_from_dd`. This is an internal function, not intended for use outside :mod:`grudge`. """ base_discr = self.discr_from_dd(dd) if not self._has_affine_groups(): # no benefit to having another discretization that takes # advantage of affine-ness from meshmode.discretization.connection import \ IdentityDiscretizationConnection return IdentityDiscretizationConnection(base_discr) base_group_factory = self.group_factory_for_discretization_tag( dd.discretization_tag) def geo_group_factory(megrp, index): from modepy.shapes import Simplex from meshmode.discretization.poly_element import \ PolynomialEquidistantSimplexElementGroup if megrp.is_affine and issubclass(megrp._modepy_shape_cls, Simplex): return PolynomialEquidistantSimplexElementGroup(megrp, order=0, index=index) else: return base_group_factory(megrp, index) from meshmode.discretization import Discretization geo_deriv_discr = Discretization(self._setup_actx, base_discr.mesh, geo_group_factory) from meshmode.discretization.connection.same_mesh import \ make_same_mesh_connection return make_same_mesh_connection(self._setup_actx, to_discr=geo_deriv_discr, from_discr=base_discr) # }}} # {{{ connection_from_dds @memoize_method 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)) # }}} # {{{ group_factory_for_discretization_tag def group_factory_for_quadrature_tag(self, discretization_tag): warn( "`DiscretizationCollection.group_factory_for_quadrature_tag` " "is deprecated and will go away in 2022. Use " "`DiscretizationCollection.group_factory_for_discretization_tag` " "instead.", DeprecationWarning, stacklevel=2) return self.group_factory_for_discretization_tag(discretization_tag) def group_factory_for_discretization_tag(self, discretization_tag): """ OK to override in user code to control mode/node choice. """ if discretization_tag is None: discretization_tag = DISCR_TAG_BASE return self.discr_tag_to_group_factory[discretization_tag] # }}} @memoize_method def _discr_tag_volume_discr(self, discretization_tag): assert discretization_tag is not None # Refuse to re-make the volume discretization if discretization_tag is DISCR_TAG_BASE: return self._volume_discr from meshmode.discretization import Discretization return Discretization( self._setup_actx, self._volume_discr.mesh, self.group_factory_for_discretization_tag(discretization_tag)) @memoize_method 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)) # {{{ connection factories: modal<->nodal @memoize_method def _modal_to_nodal_connection(self, to_dd): """ :arg to_dd: a :class:`grudge.dof_desc.DOFDesc` describing the dofs corresponding to the *to_discr* """ from meshmode.discretization.connection import \ ModalToNodalDiscretizationConnection return ModalToNodalDiscretizationConnection( from_discr=self._modal_discr(to_dd.domain_tag), to_discr=self.discr_from_dd(to_dd)) @memoize_method def _nodal_to_modal_connection(self, from_dd): """ :arg from_dd: a :class:`grudge.dof_desc.DOFDesc` describing the dofs corresponding to the *from_discr* """ from meshmode.discretization.connection import \ NodalToModalDiscretizationConnection return NodalToModalDiscretizationConnection( from_discr=self.discr_from_dd(from_dd), to_discr=self._modal_discr(from_dd.domain_tag)) # }}} # {{{ connection factories: boundary @memoize_method def _boundary_connection(self, boundary_tag): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), boundary_tag=boundary_tag) # }}} # {{{ connection factories: interior faces @memoize_method def _interior_faces_connection(self): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), FACE_RESTR_INTERIOR, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) @memoize_method def opposite_face_connection(self): """Provides a mapping from the base volume discretization to the exterior boundary restriction on a neighboring element. This does not take into account parallel partitions. """ from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self._setup_actx, self._interior_faces_connection()) # }}} # {{{ connection factories: all-faces @memoize_method def _all_faces_volume_connection(self): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), FACE_RESTR_ALL, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) # }}} @property def dim(self): """Return the topological dimension.""" return self._volume_discr.dim @property def ambient_dim(self): """Return the dimension of the ambient space.""" return self._volume_discr.ambient_dim @property def real_dtype(self): """Return the data type used for real-valued arithmetic.""" return self._volume_discr.real_dtype @property def complex_dtype(self): """Return the data type used for complex-valued arithmetic.""" return self._volume_discr.complex_dtype @property def mesh(self): """Return the :class:`meshmode.mesh.Mesh` over which the discretization collection is built. """ return self._volume_discr.mesh def empty(self, array_context: ArrayContext, dtype=None): """Return an empty :class:`~meshmode.dof_array.DOFArray` defined at the volume nodes: :class:`grudge.dof_desc.DD_VOLUME`. :arg array_context: an :class:`~arraycontext.context.ArrayContext`. :arg dtype: type special value 'c' will result in a vector of dtype :attr:`complex_dtype`. If *None* (the default), a real vector will be returned. """ return self._volume_discr.empty(array_context, dtype) def zeros(self, array_context: ArrayContext, dtype=None): """Return a zero-initialized :class:`~meshmode.dof_array.DOFArray` defined at the volume nodes, :class:`grudge.dof_desc.DD_VOLUME`. :arg array_context: an :class:`~arraycontext.context.ArrayContext`. :arg dtype: type special value 'c' will result in a vector of dtype :attr:`complex_dtype`. If *None* (the default), a real vector will be returned. """ return self._volume_discr.zeros(array_context, dtype) def is_volume_where(self, where): return where is None or as_dofdesc(where).is_volume() @property def order(self): warn( "DiscretizationCollection.order is deprecated, " "consider using the orders of element groups instead. " "'order' will go away in 2021.", DeprecationWarning, stacklevel=2) from pytools import single_valued return single_valued(egrp.order for egrp in self._volume_discr.groups) # {{{ Discretization-specific geometric properties def nodes(self, dd=None): r"""Return the nodes of a discretization specified by *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc`, or a value convertible to one. Defaults to the base volume discretization. :returns: an object array of frozen :class:`~meshmode.dof_array.DOFArray`\ s """ if dd is None: dd = DD_VOLUME return self.discr_from_dd(dd).nodes() def normal(self, dd): r"""Get the unit normal to the specified surface discretization, *dd*. :arg dd: a :class:`~grudge.dof_desc.DOFDesc` as the surface discretization. :returns: an object array of frozen :class:`~meshmode.dof_array.DOFArray`\ s. """ from arraycontext import freeze from grudge.geometry import normal return freeze(normal(self._setup_actx, self, dd))
def test_to_fd_transfer(ctx_factory, fspace_degree, mesh_name, mesh_pars, dim): """ Make sure creating a function which projects onto one dimension then transports it is the same (up to resampling error) as projecting to one dimension on the transported mesh """ # build estimate-of-convergence recorder from pytools.convergence import EOCRecorder # dimension projecting onto -> EOCRecorder eoc_recorders = {d: EOCRecorder() for d in range(dim)} # make a computing context cl_ctx = ctx_factory() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) # Get each of the refinements of the meshmeshes and record # conversions errors for mesh_par in mesh_pars: if mesh_name in ("blob2d-order1", "blob2d-order4"): assert dim == 2 from meshmode.mesh.io import read_gmsh mm_mesh = read_gmsh(f"{mesh_name}-h{mesh_par}.msh", force_ambient_dim=dim) h = float(mesh_par) elif mesh_name == "warp": from meshmode.mesh.generation import generate_warped_rect_mesh mm_mesh = generate_warped_rect_mesh(dim, order=4, n=mesh_par) h = 1 / mesh_par else: raise ValueError("mesh_name not recognized") # Make discr and connect it to firedrake factory = InterpolatoryQuadratureSimplexGroupFactory(fspace_degree) discr = Discretization(actx, mm_mesh, factory) fdrake_connection = build_connection_to_firedrake(discr) fdrake_fspace = fdrake_connection.firedrake_fspace() spatial_coord = SpatialCoordinate(fdrake_fspace.mesh()) # get the group's nodes in a numpy array nodes = discr.nodes() group_nodes = np.array( [actx.to_numpy(dof_arr[0]) for dof_arr in nodes]) for d in range(dim): meshmode_f = discr.zeros(actx) meshmode_f[0][:] = group_nodes[d, :, :] # connect to firedrake and evaluate expr in firedrake fdrake_f = Function(fdrake_fspace).interpolate(spatial_coord[d]) # transport to firedrake and record error mm2fd_f = fdrake_connection.from_meshmode(meshmode_f) err = np.max(np.abs(fdrake_f.dat.data - mm2fd_f.dat.data)) eoc_recorders[d].add_data_point(h, err) # assert that order is correct or error is "low enough" for d, eoc_rec in eoc_recorders.items(): print("\nvector *x* -> *x[%s]*\n" % d, eoc_rec) assert (eoc_rec.order_estimate() >= fspace_degree or eoc_rec.max_error() < 2e-14)
def timing_run(nx, ny, visualize=False): import logging logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) actx = PyOpenCLArrayContext(queue) mesh = make_mesh(nx=nx, ny=ny, visualize=visualize) density_discr = Discretization( actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import (QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource(density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order) places = {"qbx": qbx} if visualize: from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) targets = PointsTarget(actx.from_numpy(fplot.points)) places.update({ "plot-targets": targets, "qbx-indicator": qbx.copy(target_association_tolerance=0.05, fmm_level_to_order=lambda lev: 7, qbx_order=2), "qbx-target-assoc": qbx.copy(target_association_tolerance=0.1) }) from pytential import GeometryCollection places = GeometryCollection(places, auto_where="qbx") density_discr = places.get_discretization("qbx") # {{{ describe bvp from sumpy.kernel import HelmholtzKernel kernel = HelmholtzKernel(2) sigma_sym = sym.var("sigma") sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = sym.cse(sigma_sym / sqrt_w) # Brakhage-Werner parameter alpha = 1j # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 k_sym = sym.var("k") S_sym = sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit=+1) D_sym = sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit="avg") bdry_op_sym = -loc_sign * 0.5 * sigma_sym + sqrt_w * (alpha * S_sym + D_sym) # }}} bound_op = bind(places, bdry_op_sym) # {{{ fix rhs and solve mode_nr = 3 from meshmode.dof_array import thaw nodes = thaw(actx, density_discr.nodes()) angle = actx.np.arctan2(nodes[1], nodes[0]) sigma = actx.np.cos(mode_nr * angle) # }}} # {{{ postprocess/visualize repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=+1) sym_op = sym.S(kernel, sym.var("sigma"), **repr_kwargs) bound_op = bind(places, sym_op) print("FMM WARM-UP RUN 1: %5d elements" % mesh.nelements) bound_op(actx, sigma=sigma, k=k) queue.finish() print("FMM WARM-UP RUN 2: %5d elements" % mesh.nelements) bound_op(actx, sigma=sigma, k=k) queue.finish() from time import time t_start = time() bound_op(actx, sigma=sigma, k=k) actx.queue.finish() elapsed = time() - t_start print("FMM TIMING RUN: %5d elements -> %g s" % (mesh.nelements, elapsed)) if visualize: ones_density = density_discr.zeros(queue) ones_density.fill(1) indicator = bind(places, sym_op, auto_where=("qbx-indicator", "plot-targets"))( queue, sigma=ones_density).get() try: fld_in_vol = bind(places, sym_op, auto_where=("qbx-target-assoc", "plot-targets"))(queue, sigma=sigma, k=k).get() except QBXTargetAssociationFailedException as e: fplot.write_vtk_file("scaling-study-failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)), ]) raise fplot.write_vtk_file("scaling-study-potential.vts", [ ("potential", fld_in_vol), ("indicator", indicator), ]) return (mesh.nelements, elapsed)
class DGDiscretization: def __init__(self, cl_ctx, mesh, order): self.order = order from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory self.group_factory = PolynomialWarpAndBlendGroupFactory(order=order) self.volume_discr = Discretization(cl_ctx, mesh, self.group_factory) assert self.volume_discr.dim == 2 @property def cl_context(self): return self.volume_discr.cl_context @property def dim(self): return self.volume_discr.dim # {{{ discretizations/connections @memoize_method def boundary_connection(self, boundary_tag): from meshmode.discretization.connection import make_face_restriction return make_face_restriction(self.volume_discr, self.group_factory, boundary_tag=boundary_tag) @memoize_method def interior_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_INTERIOR) return make_face_restriction(self.volume_discr, self.group_factory, FACE_RESTR_INTERIOR, per_face_groups=False) @memoize_method def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self.interior_faces_connection()) @memoize_method def all_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_ALL) return make_face_restriction(self.volume_discr, self.group_factory, FACE_RESTR_ALL, per_face_groups=False) @memoize_method def get_to_all_face_embedding(self, where): from meshmode.discretization.connection import \ make_face_to_all_faces_embedding faces_conn = self.get_connection("vol", where) return make_face_to_all_faces_embedding(faces_conn, self.get_discr("all_faces")) def get_connection(self, src, tgt): src_tgt = (src, tgt) if src_tgt == ("vol", "int_faces"): return self.interior_faces_connection() elif src_tgt == ("vol", "all_faces"): return self.all_faces_connection() elif src_tgt == ("vol", BTAG_ALL): return self.boundary_connection(tgt) elif src_tgt == ("int_faces", "all_faces"): return self.get_to_all_face_embedding(src) elif src_tgt == (BTAG_ALL, "all_faces"): return self.get_to_all_face_embedding(src) else: raise ValueError(f"locations '{src}'->'{tgt}' not understood") def interp(self, src, tgt, vec): if is_obj_array(vec): return with_object_array_or_scalar( lambda el: self.interp(src, tgt, el), vec) return self.get_connection(src, tgt)(vec.queue, vec) def get_discr(self, where): if where == "vol": return self.volume_discr elif where == "all_faces": return self.all_faces_connection().to_discr elif where == "int_faces": return self.interior_faces_connection().to_discr elif where == BTAG_ALL: return self.boundary_connection(where).to_discr else: raise ValueError(f"location '{where}' not understood") # }}} @memoize_method def parametrization_derivative(self): with cl.CommandQueue(self.cl_context) as queue: return without_queue( parametrization_derivative(queue, self.volume_discr)) @memoize_method def vol_jacobian(self): with cl.CommandQueue(self.cl_context) as queue: [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) return (a * d - b * c).with_queue(None) @memoize_method def inverse_parametrization_derivative(self): with cl.CommandQueue(self.cl_context) as queue: [a, b], [c, d] = with_queue(queue, self.parametrization_derivative()) result = np.zeros((2, 2), dtype=object) det = a * d - b * c result[0, 0] = d / det result[0, 1] = -b / det result[1, 0] = -c / det result[1, 1] = a / det return without_queue(result) def zeros(self, queue): return self.volume_discr.zeros(queue) def grad(self, vec): ipder = self.inverse_parametrization_derivative() queue = vec.queue dref = [ self.volume_discr.num_reference_derivative(queue, (idim, ), vec).with_queue(queue) for idim in range(self.volume_discr.dim) ] return make_obj_array([ sum(dref_i * ipder_i for dref_i, ipder_i in zip(dref, ipder[iambient])) for iambient in range(self.volume_discr.ambient_dim) ]) def div(self, vecs): return sum(self.grad(vec_i)[i] for i, vec_i in enumerate(vecs)) @memoize_method def normal(self, where): bdry_discr = self.get_discr(where) with cl.CommandQueue(self.cl_context) as queue: ((a, ), (b, )) = with_queue(queue, parametrization_derivative(queue, bdry_discr)) nrm = 1 / (a**2 + b**2)**0.5 return without_queue(join_fields(b * nrm, -a * nrm)) @memoize_method def face_jacobian(self, where): bdry_discr = self.get_discr(where) with cl.CommandQueue(self.cl_context) as queue: ((a, ), (b, )) = with_queue(queue, parametrization_derivative(queue, bdry_discr)) return ((a**2 + b**2)**0.5).with_queue(None) @memoize_method def get_inverse_mass_matrix(self, grp, dtype): import modepy as mp matrix = mp.inverse_mass_matrix(grp.basis(), grp.unit_nodes) with cl.CommandQueue(self.cl_context) as queue: return (cla.to_device(queue, matrix).with_queue(None)) def inverse_mass(self, vec): if is_obj_array(vec): return with_object_array_or_scalar( lambda el: self.inverse_mass(el), vec) @memoize_in(self, "elwise_linear_knl") def knl(): knl = lp.make_kernel("""{[k,i,j]: 0<=k<nelements and 0<=i<ndiscr_nodes_out and 0<=j<ndiscr_nodes_in}""", "result[k,i] = sum(j, mat[i, j] * vec[k, j])", default_offset=lp.auto, name="diff") knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") return lp.tag_inames(knl, dict(k="g.0")) discr = self.volume_discr result = discr.empty(queue=vec.queue, dtype=vec.dtype) for grp in discr.groups: matrix = self.get_inverse_mass_matrix(grp, vec.dtype) knl()(vec.queue, mat=matrix, result=grp.view(result), vec=grp.view(vec)) return result / self.vol_jacobian() @memoize_method def get_local_face_mass_matrix(self, afgrp, volgrp, dtype): nfaces = volgrp.mesh_el_group.nfaces assert afgrp.nelements == nfaces * volgrp.nelements matrix = np.empty((volgrp.nunit_nodes, nfaces, afgrp.nunit_nodes), dtype=dtype) from modepy.tools import UNIT_VERTICES import modepy as mp for iface, fvi in enumerate( volgrp.mesh_el_group.face_vertex_indices()): face_vertices = UNIT_VERTICES[volgrp.dim][np.array(fvi)].T matrix[:, iface, :] = mp.nodal_face_mass_matrix( volgrp.basis(), volgrp.unit_nodes, afgrp.unit_nodes, volgrp.order, face_vertices) with cl.CommandQueue(self.cl_context) as queue: return (cla.to_device(queue, matrix).with_queue(None)) def face_mass(self, vec): if is_obj_array(vec): return with_object_array_or_scalar(lambda el: self.face_mass(el), vec) @memoize_in(self, "face_mass_knl") def knl(): knl = lp.make_kernel( """{[k,i,f,j]: 0<=k<nelements and 0<=f<nfaces and 0<=i<nvol_nodes and 0<=j<nface_nodes}""", "result[k,i] = sum(f, sum(j, mat[i, f, j] * vec[f, k, j]))", default_offset=lp.auto, name="face_mass") knl = lp.split_iname(knl, "i", 16, inner_tag="l.0") return lp.tag_inames(knl, dict(k="g.0")) all_faces_conn = self.get_connection("vol", "all_faces") all_faces_discr = all_faces_conn.to_discr vol_discr = all_faces_conn.from_discr result = vol_discr.empty(queue=vec.queue, dtype=vec.dtype) fj = self.face_jacobian("all_faces") vec = vec * fj assert len(all_faces_discr.groups) == len(vol_discr.groups) for afgrp, volgrp in zip(all_faces_discr.groups, vol_discr.groups): nfaces = volgrp.mesh_el_group.nfaces matrix = self.get_local_face_mass_matrix(afgrp, volgrp, vec.dtype) input_view = afgrp.view(vec).reshape(nfaces, volgrp.nelements, afgrp.nunit_nodes) knl()(vec.queue, mat=matrix, result=volgrp.view(result), vec=input_view) return result
def timing_run(nx, ny): import logging logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) mesh = make_mesh(nx=nx, ny=ny) density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import ( QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource( density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order ) # {{{ describe bvp from sumpy.kernel import HelmholtzKernel kernel = HelmholtzKernel(2) cse = sym.cse sigma_sym = sym.var("sigma") sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = cse(sigma_sym/sqrt_w) # Brakhage-Werner parameter alpha = 1j # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 bdry_op_sym = (-loc_sign*0.5*sigma_sym + sqrt_w*( alpha*sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k")) - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k")) )) # }}} bound_op = bind(qbx, bdry_op_sym) # {{{ fix rhs and solve mode_nr = 3 nodes = density_discr.nodes().with_queue(queue) angle = cl.clmath.atan2(nodes[1], nodes[0]) sigma = cl.clmath.cos(mode_nr*angle) # }}} # {{{ postprocess/visualize repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=+1) sym_op = sym.S(kernel, sym.var("sigma"), **repr_kwargs) bound_op = bind(qbx, sym_op) print("FMM WARM-UP RUN 1: %d elements" % mesh.nelements) bound_op(queue, sigma=sigma, k=k) print("FMM WARM-UP RUN 2: %d elements" % mesh.nelements) bound_op(queue, sigma=sigma, k=k) queue.finish() print("FMM TIMING RUN: %d elements" % mesh.nelements) from time import time t_start = time() bound_op(queue, sigma=sigma, k=k) queue.finish() elapsed = time()-t_start print("FMM TIMING RUN DONE: %d elements -> %g s" % (mesh.nelements, elapsed)) return (mesh.nelements, elapsed) if 0: from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) targets = cl.array.to_device(queue, fplot.points) qbx_tgt_tol = qbx.copy(target_association_tolerance=0.05) indicator_qbx = qbx_tgt_tol.copy( fmm_level_to_order=lambda lev: 7, qbx_order=2) ones_density = density_discr.zeros(queue) ones_density.fill(1) indicator = bind( (indicator_qbx, PointsTarget(targets)), sym_op)( queue, sigma=ones_density).get() qbx_stick_out = qbx.copy(target_stick_out_factor=0.1) try: fld_in_vol = bind( (qbx_stick_out, PointsTarget(targets)), sym_op)(queue, sigma=sigma, k=k).get() except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( "failed-targets.vts", [ ("failed", e.failed_target_flags.get(queue)) ] ) raise #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file( "potential-scaling.vts", [ ("potential", fld_in_vol), ("indicator", indicator) ] )
class DGDiscretizationWithBoundaries(DiscretizationBase): """ .. automethod :: discr_from_dd .. automethod :: connection_from_dds .. autoattribute :: cl_context .. autoattribute :: dim .. autoattribute :: ambient_dim .. autoattribute :: mesh .. automethod :: empty .. automethod :: zeros """ def __init__(self, cl_ctx, mesh, order, quad_tag_to_group_factory=None, mpi_communicator=None): """ :param quad_tag_to_group_factory: A mapping from quadrature tags (typically strings--but may be any hashable/comparable object) to a :class:`meshmode.discretization.ElementGroupFactory` indicating with which quadrature discretization the operations are to be carried out, or *None* to indicate that operations with this quadrature tag should be carried out with the standard volume discretization. """ if quad_tag_to_group_factory is None: quad_tag_to_group_factory = {} self.order = order self.quad_tag_to_group_factory = quad_tag_to_group_factory from meshmode.discretization import Discretization self._volume_discr = Discretization( cl_ctx, mesh, self.group_factory_for_quadrature_tag(sym.QTAG_NONE)) # {{{ management of discretization-scoped common subexpressions from pytools import UniqueNameGenerator self._discr_scoped_name_gen = UniqueNameGenerator() self._discr_scoped_subexpr_to_name = {} self._discr_scoped_subexpr_name_to_value = {} # }}} with cl.CommandQueue(cl_ctx) as queue: self._dist_boundary_connections = \ self._set_up_distributed_communication(mpi_communicator, queue) self.mpi_communicator = mpi_communicator def get_management_rank_index(self): return 0 def is_management_rank(self): if self.mpi_communicator is None: return True else: return self.mpi_communicator.Get_rank() \ == self._get_management_rank_index() def _set_up_distributed_communication(self, mpi_communicator, queue): from_dd = sym.DOFDesc("vol", sym.QTAG_NONE) from meshmode.distributed import get_connected_partitions connected_parts = get_connected_partitions(self._volume_discr.mesh) if mpi_communicator is None and connected_parts: raise RuntimeError("must supply an MPI communicator when using a " "distributed mesh") grp_factory = self.group_factory_for_quadrature_tag(sym.QTAG_NONE) setup_helpers = {} boundary_connections = {} from meshmode.distributed import MPIBoundaryCommSetupHelper for i_remote_part in connected_parts: conn = self.connection_from_dds( from_dd, sym.DOFDesc(sym.BTAG_PARTITION(i_remote_part), sym.QTAG_NONE)) setup_helper = setup_helpers[ i_remote_part] = MPIBoundaryCommSetupHelper( mpi_communicator, queue, conn, i_remote_part, grp_factory) setup_helper.post_sends() for i_remote_part, setup_helper in six.iteritems(setup_helpers): boundary_connections[i_remote_part] = setup_helper.complete_setup() return boundary_connections def get_distributed_boundary_swap_connection(self, dd): if dd.quadrature_tag != sym.QTAG_NONE: # FIXME raise NotImplementedError( "Distributed communication with quadrature") assert isinstance(dd.domain_tag, sym.BTAG_PARTITION) return self._dist_boundary_connections[dd.domain_tag.part_nr] @memoize_method def discr_from_dd(self, dd): dd = sym.as_dofdesc(dd) qtag = dd.quadrature_tag if dd.is_volume(): if qtag is not sym.QTAG_NONE: return self._quad_volume_discr(qtag) return self._volume_discr if qtag is not sym.QTAG_NONE: no_quad_discr = self.discr_from_dd(sym.DOFDesc(dd.domain_tag)) from meshmode.discretization import Discretization return Discretization(self._volume_discr.cl_context, no_quad_discr.mesh, self.group_factory_for_quadrature_tag(qtag)) assert qtag is sym.QTAG_NONE if dd.domain_tag is sym.FACE_RESTR_ALL: return self._all_faces_volume_connection().to_discr elif dd.domain_tag is sym.FACE_RESTR_INTERIOR: return self._interior_faces_connection().to_discr elif dd.is_boundary(): return self._boundary_connection(dd.domain_tag).to_discr else: raise ValueError("DOF desc tag not understood: " + str(dd)) @memoize_method def connection_from_dds(self, from_dd, to_dd): from_dd = sym.as_dofdesc(from_dd) to_dd = sym.as_dofdesc(to_dd) to_qtag = to_dd.quadrature_tag if (not from_dd.is_volume() and from_dd.quadrature_tag == to_dd.quadrature_tag and to_dd.domain_tag is sym.FACE_RESTR_ALL): faces_conn = self.connection_from_dds( sym.DOFDesc("vol"), sym.DOFDesc(from_dd.domain_tag)) from meshmode.discretization.connection import \ make_face_to_all_faces_embedding return make_face_to_all_faces_embedding( faces_conn, self.discr_from_dd(to_dd), self.discr_from_dd(from_dd)) # {{{ simplify domain + qtag change into chained if (from_dd.domain_tag != to_dd.domain_tag and from_dd.quadrature_tag is sym.QTAG_NONE and to_dd.quadrature_tag is not sym.QTAG_NONE): from meshmode.discretization.connection import \ ChainedDiscretizationConnection intermediate_dd = sym.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 if (from_dd.domain_tag == to_dd.domain_tag and from_dd.quadrature_tag is sym.QTAG_NONE and to_dd.quadrature_tag is not sym.QTAG_NONE): from meshmode.discretization.connection.same_mesh import \ make_same_mesh_connection return make_same_mesh_connection(self.discr_from_dd(to_dd), self.discr_from_dd(from_dd)) # }}} if from_dd.quadrature_tag is not sym.QTAG_NONE: raise ValueError("cannot interpolate *from* a " "(non-interpolatory) quadrature grid") assert to_qtag is sym.QTAG_NONE if from_dd.is_volume(): if to_dd.domain_tag is sym.FACE_RESTR_ALL: return self._all_faces_volume_connection() if to_dd.domain_tag is sym.FACE_RESTR_INTERIOR: return self._interior_faces_connection() elif to_dd.is_boundary(): assert from_dd.quadrature_tag is sym.QTAG_NONE return self._boundary_connection(to_dd.domain_tag) elif to_dd.is_volume(): from meshmode.discretization.connection import \ make_same_mesh_connection to_discr = self._quad_volume_discr(to_dd.quadrature_tag) from_discr = self._volume_discr return make_same_mesh_connection(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 group_factory_for_quadrature_tag(self, quadrature_tag): """ OK to override in user code to control mode/node choice. """ if quadrature_tag is None: quadrature_tag = sym.QTAG_NONE from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory if quadrature_tag is not sym.QTAG_NONE: return self.quad_tag_to_group_factory[quadrature_tag] else: return PolynomialWarpAndBlendGroupFactory(order=self.order) @memoize_method def _quad_volume_discr(self, quadrature_tag): from meshmode.discretization import Discretization return Discretization( self._volume_discr.cl_context, self._volume_discr.mesh, self.group_factory_for_quadrature_tag(quadrature_tag)) # {{{ boundary @memoize_method def _boundary_connection(self, boundary_tag): from meshmode.discretization.connection import make_face_restriction return make_face_restriction(self._volume_discr, self.group_factory_for_quadrature_tag( sym.QTAG_NONE), boundary_tag=boundary_tag) # }}} # {{{ interior faces @memoize_method def _interior_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_INTERIOR) return make_face_restriction( self._volume_discr, self.group_factory_for_quadrature_tag(sym.QTAG_NONE), FACE_RESTR_INTERIOR, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) @memoize_method def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self._interior_faces_connection()) # }}} # {{{ all-faces @memoize_method def _all_faces_volume_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_ALL) return make_face_restriction( self._volume_discr, self.group_factory_for_quadrature_tag(sym.QTAG_NONE), FACE_RESTR_ALL, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) # }}} @property def cl_context(self): return self._volume_discr.cl_context @property def dim(self): return self._volume_discr.dim @property def ambient_dim(self): return self._volume_discr.ambient_dim @property def real_dtype(self): return self._volume_discr.real_dtype @property def complex_dtype(self): return self._volume_discr.complex_dtype @property def mesh(self): return self._volume_discr.mesh def empty(self, queue=None, dtype=None, extra_dims=None, allocator=None): return self._volume_discr.empty(queue, dtype, extra_dims=extra_dims, allocator=allocator) def zeros(self, queue, dtype=None, extra_dims=None, allocator=None): return self._volume_discr.zeros(queue, dtype, extra_dims=extra_dims, allocator=allocator) def is_volume_where(self, where): from grudge import sym return (where is None or where == sym.VTAG_ALL)
class DGDiscretization: def __init__(self, actx, mesh, order): self.order = order from meshmode.discretization import Discretization from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory self.group_factory = PolynomialWarpAndBlendGroupFactory(order=order) self.volume_discr = Discretization(actx, mesh, self.group_factory) assert self.volume_discr.dim == 2 @property def _setup_actx(self): return self.volume_discr._setup_actx @property def dim(self): return self.volume_discr.dim # {{{ discretizations/connections @memoize_method def boundary_connection(self, boundary_tag): from meshmode.discretization.connection import make_face_restriction return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, boundary_tag=boundary_tag) @memoize_method def interior_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_INTERIOR) return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_INTERIOR, per_face_groups=False) @memoize_method def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self._setup_actx, self.interior_faces_connection()) @memoize_method def all_faces_connection(self): from meshmode.discretization.connection import (make_face_restriction, FACE_RESTR_ALL) return make_face_restriction(self.volume_discr._setup_actx, self.volume_discr, self.group_factory, FACE_RESTR_ALL, per_face_groups=False) @memoize_method def get_to_all_face_embedding(self, where): from meshmode.discretization.connection import \ make_face_to_all_faces_embedding faces_conn = self.get_connection("vol", where) return make_face_to_all_faces_embedding(self._setup_actx, faces_conn, self.get_discr("all_faces")) def get_connection(self, src, tgt): src_tgt = (src, tgt) if src_tgt == ("vol", "int_faces"): return self.interior_faces_connection() elif src_tgt == ("vol", "all_faces"): return self.all_faces_connection() elif src_tgt == ("vol", BTAG_ALL): return self.boundary_connection(tgt) elif src_tgt == ("int_faces", "all_faces"): return self.get_to_all_face_embedding(src) elif src_tgt == (BTAG_ALL, "all_faces"): return self.get_to_all_face_embedding(src) else: raise ValueError(f"locations '{src}'->'{tgt}' not understood") def interp(self, src, tgt, vec): return self.get_connection(src, tgt)(vec) def get_discr(self, where): if where == "vol": return self.volume_discr elif where == "all_faces": return self.all_faces_connection().to_discr elif where == "int_faces": return self.interior_faces_connection().to_discr elif where == BTAG_ALL: return self.boundary_connection(where).to_discr else: raise ValueError(f"location '{where}' not understood") # }}} @memoize_method def parametrization_derivative(self): return freeze( parametrization_derivative(self._setup_actx, self.volume_discr)) @memoize_method def vol_jacobian(self): [a, b], [c, d] = thaw(self.parametrization_derivative(), self._setup_actx) return freeze(a * d - b * c) @memoize_method def inverse_parametrization_derivative(self): [a, b], [c, d] = thaw(self.parametrization_derivative(), self._setup_actx) result = np.zeros((2, 2), dtype=object) det = a * d - b * c result[0, 0] = d / det result[0, 1] = -b / det result[1, 0] = -c / det result[1, 1] = a / det return freeze(result) def zeros(self, actx): return self.volume_discr.zeros(actx) def grad(self, vec): ipder = thaw(self.inverse_parametrization_derivative(), vec.array_context) from meshmode.discretization import num_reference_derivative dref = [ num_reference_derivative(self.volume_discr, (idim, ), vec) for idim in range(self.volume_discr.dim) ] return make_obj_array([ sum(dref_i * ipder_i for dref_i, ipder_i in zip(dref, ipder[iambient])) for iambient in range(self.volume_discr.ambient_dim) ]) def div(self, vecs): return sum(self.grad(vec_i)[i] for i, vec_i in enumerate(vecs)) @memoize_method def normal(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) nrm = 1 / (a**2 + b**2)**0.5 return freeze(flat_obj_array(b * nrm, -a * nrm)) @memoize_method def face_jacobian(self, where): bdry_discr = self.get_discr(where) ((a, ), (b, )) = parametrization_derivative(self._setup_actx, bdry_discr) return freeze((a**2 + b**2)**0.5) @memoize_method def get_inverse_mass_matrix(self, grp, dtype): import modepy as mp matrix = mp.inverse_mass_matrix(grp.basis_obj().functions, grp.unit_nodes) actx = self._setup_actx return actx.freeze(actx.from_numpy(matrix)) def inverse_mass(self, vec): if not isinstance(vec, DOFArray): return map_array_container(self.inverse_mass, vec) actx = vec.array_context dtype = vec.entry_dtype discr = self.volume_discr return DOFArray( actx, data=tuple( actx.einsum("ij,ej->ei", self.get_inverse_mass_matrix(grp, dtype), vec_i, arg_names=("mass_inv_mat", "vec"), tagged=(FirstAxisIsElementsTag(), )) for grp, vec_i in zip(discr.groups, vec))) / thaw( self.vol_jacobian(), actx) @memoize_method def get_local_face_mass_matrix(self, afgrp, volgrp, dtype): nfaces = volgrp.mesh_el_group.nfaces assert afgrp.nelements == nfaces * volgrp.nelements matrix = np.empty((volgrp.nunit_dofs, nfaces, afgrp.nunit_dofs), dtype=dtype) import modepy as mp shape = mp.Simplex(volgrp.dim) unit_vertices = mp.unit_vertices_for_shape(shape).T for face in mp.faces_for_shape(shape): face_vertices = unit_vertices[np.array( face.volume_vertex_indices)].T matrix[:, face.face_index, :] = mp.nodal_face_mass_matrix( volgrp.basis_obj().functions, volgrp.unit_nodes, afgrp.unit_nodes, volgrp.order, face_vertices) actx = self._setup_actx return actx.freeze(actx.from_numpy(matrix)) def face_mass(self, vec): if not isinstance(vec, DOFArray): return map_array_container(self.face_mass, vec) actx = vec.array_context dtype = vec.entry_dtype @memoize_in(self, "face_mass_knl") def knl(): return make_loopy_program( """{[iel,idof,f,j]: 0<=iel<nelements and 0<=f<nfaces and 0<=idof<nvol_nodes and 0<=j<nface_nodes}""", "result[iel,idof] = " "sum(f, sum(j, mat[idof, f, j] * vec[f, iel, j]))", name="face_mass") all_faces_conn = self.get_connection("vol", "all_faces") all_faces_discr = all_faces_conn.to_discr vol_discr = all_faces_conn.from_discr fj = thaw(self.face_jacobian("all_faces"), vec.array_context) vec = vec * fj assert len(all_faces_discr.groups) == len(vol_discr.groups) return DOFArray( actx, data=tuple( actx.call_loopy( knl(), mat=self.get_local_face_mass_matrix(afgrp, volgrp, dtype), vec=vec_i.reshape(volgrp.mesh_el_group.nfaces, volgrp. nelements, afgrp.nunit_dofs))["result"] for afgrp, volgrp, vec_i in zip(all_faces_discr.groups, vol_discr.groups, vec)))
def timing_run(nx, ny): import logging logging.basicConfig(level=logging.WARNING) # INFO for more progress info cl_ctx = cl.create_some_context() queue = cl.CommandQueue(cl_ctx) mesh = make_mesh(nx=nx, ny=ny) density_discr = Discretization( cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order)) from pytential.qbx import (QBXLayerPotentialSource, QBXTargetAssociationFailedException) qbx = QBXLayerPotentialSource(density_discr, fine_order=bdry_ovsmp_quad_order, qbx_order=qbx_order, fmm_order=fmm_order) # {{{ describe bvp from sumpy.kernel import HelmholtzKernel kernel = HelmholtzKernel(2) cse = sym.cse sigma_sym = sym.var("sigma") sqrt_w = sym.sqrt_jac_q_weight(2) inv_sqrt_w_sigma = cse(sigma_sym / sqrt_w) # Brakhage-Werner parameter alpha = 1j # -1 for interior Dirichlet # +1 for exterior Dirichlet loc_sign = +1 bdry_op_sym = (-loc_sign * 0.5 * sigma_sym + sqrt_w * (alpha * sym.S(kernel, inv_sqrt_w_sigma, k=sym.var("k")) - sym.D(kernel, inv_sqrt_w_sigma, k=sym.var("k")))) # }}} bound_op = bind(qbx, bdry_op_sym) # {{{ fix rhs and solve mode_nr = 3 nodes = density_discr.nodes().with_queue(queue) angle = cl.clmath.atan2(nodes[1], nodes[0]) sigma = cl.clmath.cos(mode_nr * angle) # }}} # {{{ postprocess/visualize repr_kwargs = dict(k=sym.var("k"), qbx_forced_limit=+1) sym_op = sym.S(kernel, sym.var("sigma"), **repr_kwargs) bound_op = bind(qbx, sym_op) print("FMM WARM-UP RUN 1: %d elements" % mesh.nelements) bound_op(queue, sigma=sigma, k=k) print("FMM WARM-UP RUN 2: %d elements" % mesh.nelements) bound_op(queue, sigma=sigma, k=k) queue.finish() print("FMM TIMING RUN: %d elements" % mesh.nelements) from time import time t_start = time() bound_op(queue, sigma=sigma, k=k) queue.finish() elapsed = time() - t_start print("FMM TIMING RUN DONE: %d elements -> %g s" % (mesh.nelements, elapsed)) return (mesh.nelements, elapsed) if 0: from sumpy.visualization import FieldPlotter fplot = FieldPlotter(np.zeros(2), extent=5, npoints=1500) targets = cl.array.to_device(queue, fplot.points) qbx_tgt_tol = qbx.copy(target_association_tolerance=0.05) indicator_qbx = qbx_tgt_tol.copy(fmm_level_to_order=lambda lev: 7, qbx_order=2) ones_density = density_discr.zeros(queue) ones_density.fill(1) indicator = bind((indicator_qbx, PointsTarget(targets)), sym_op)(queue, sigma=ones_density).get() qbx_stick_out = qbx.copy(target_stick_out_factor=0.1) try: fld_in_vol = bind((qbx_stick_out, PointsTarget(targets)), sym_op)(queue, sigma=sigma, k=k).get() except QBXTargetAssociationFailedException as e: fplot.write_vtk_file( "failed-targets.vts", [("failed", e.failed_target_flags.get(queue))]) raise #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5) fplot.write_vtk_file("potential-scaling.vts", [("potential", fld_in_vol), ("indicator", indicator)])
class DiscretizationCollection: """ .. automethod :: __init__ .. automethod :: discr_from_dd .. automethod :: connection_from_dds .. autoattribute :: dim .. autoattribute :: ambient_dim .. autoattribute :: mesh .. automethod :: empty .. automethod :: zeros """ def __init__( self, array_context, mesh, order=None, discr_tag_to_group_factory=None, mpi_communicator=None, # FIXME: `quad_tag_to_group_factory` is deprecated quad_tag_to_group_factory=None): """ :param discr_tag_to_group_factory: A mapping from discretization tags (typically one of: :class:`grudge.dof_desc.DISCR_TAG_BASE`, :class:`grudge.dof_desc.DISCR_TAG_MODAL`, or :class:`grudge.dof_desc.DISCR_TAG_QUAD`) to a :class:`~meshmode.discretization.poly_element.ElementGroupFactory` indicating with which type of discretization the operations are to be carried out, or *None* to indicate that operations with this discretization tag should be carried out with the standard volume discretization. """ if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is not None): raise ValueError( "Both `quad_tag_to_group_factory` and `discr_tag_to_group_factory` " "are specified. Use `discr_tag_to_group_factory` instead.") # FIXME: `quad_tag_to_group_factory` is deprecated if (quad_tag_to_group_factory is not None and discr_tag_to_group_factory is None): warn( "`quad_tag_to_group_factory` is a deprecated kwarg and will " "be dropped in version 2022.x. Use `discr_tag_to_group_factory` " "instead.", DeprecationWarning, stacklevel=2) discr_tag_to_group_factory = quad_tag_to_group_factory self._setup_actx = array_context from meshmode.discretization.poly_element import \ PolynomialWarpAndBlendGroupFactory if discr_tag_to_group_factory is None: if order is None: raise TypeError( "one of 'order' and 'discr_tag_to_group_factory' must be given" ) discr_tag_to_group_factory = { DISCR_TAG_BASE: PolynomialWarpAndBlendGroupFactory(order=order) } else: if order is not None: discr_tag_to_group_factory = discr_tag_to_group_factory.copy() if DISCR_TAG_BASE in discr_tag_to_group_factory: raise ValueError( "if 'order' is given, 'discr_tag_to_group_factory' must " "not have a key of DISCR_TAG_BASE") discr_tag_to_group_factory[DISCR_TAG_BASE] = \ PolynomialWarpAndBlendGroupFactory(order=order) # Modal discr should always comes from the base discretization discr_tag_to_group_factory[DISCR_TAG_MODAL] = \ _generate_modal_group_factory( discr_tag_to_group_factory[DISCR_TAG_BASE] ) self.discr_tag_to_group_factory = discr_tag_to_group_factory from meshmode.discretization import Discretization self._volume_discr = Discretization( array_context, mesh, self.group_factory_for_discretization_tag(DISCR_TAG_BASE)) # {{{ management of discretization-scoped common subexpressions from pytools import UniqueNameGenerator self._discr_scoped_name_gen = UniqueNameGenerator() self._discr_scoped_subexpr_to_name = {} self._discr_scoped_subexpr_name_to_value = {} # }}} self._dist_boundary_connections = \ self._set_up_distributed_communication( mpi_communicator, array_context) self.mpi_communicator = mpi_communicator @property def quad_tag_to_group_factory(self): warn( "`DiscretizationCollection.quad_tag_to_group_factory` " "is deprecated and will go away in 2022. Use " "`DiscretizationCollection.discr_tag_to_group_factory` " "instead.", DeprecationWarning, stacklevel=2) return self.discr_tag_to_group_factory def get_management_rank_index(self): return 0 def is_management_rank(self): if self.mpi_communicator is None: return True else: return self.mpi_communicator.Get_rank() \ == self.get_management_rank_index() 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 get_distributed_boundary_swap_connection(self, dd): if dd.discretization_tag is not DISCR_TAG_BASE: # FIXME raise NotImplementedError( "Distributed communication with discretization tag " f"{dd.discretization_tag} is not implemented.") assert isinstance(dd.domain_tag, DTAG_BOUNDARY) assert isinstance(dd.domain_tag.tag, BTAG_PARTITION) return self._dist_boundary_connections[dd.domain_tag.tag.part_nr] @memoize_method 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)) @memoize_method def connection_from_dds(self, from_dd, to_dd): 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 group_factory_for_quadrature_tag(self, discretization_tag): warn( "`DiscretizationCollection.group_factory_for_quadrature_tag` " "is deprecated and will go away in 2022. Use " "`DiscretizationCollection.group_factory_for_discretization_tag` " "instead.", DeprecationWarning, stacklevel=2) return self.group_factory_for_discretization_tag(discretization_tag) def group_factory_for_discretization_tag(self, discretization_tag): """ OK to override in user code to control mode/node choice. """ if discretization_tag is None: discretization_tag = DISCR_TAG_BASE return self.discr_tag_to_group_factory[discretization_tag] @memoize_method def _discr_tag_volume_discr(self, discretization_tag): from meshmode.discretization import Discretization return Discretization( self._setup_actx, self._volume_discr.mesh, self.group_factory_for_discretization_tag(discretization_tag)) # {{{ modal to nodal connections @memoize_method 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)) @memoize_method def _modal_to_nodal_connection(self, to_dd): """ :arg to_dd: a :class:`grudge.dof_desc.DOFDesc` describing the dofs corresponding to the *to_discr* """ from meshmode.discretization.connection import \ ModalToNodalDiscretizationConnection return ModalToNodalDiscretizationConnection( from_discr=self._modal_discr(to_dd.domain_tag), to_discr=self.discr_from_dd(to_dd)) @memoize_method def _nodal_to_modal_connection(self, from_dd): """ :arg from_dd: a :class:`grudge.dof_desc.DOFDesc` describing the dofs corresponding to the *from_discr* """ from meshmode.discretization.connection import \ NodalToModalDiscretizationConnection return NodalToModalDiscretizationConnection( from_discr=self.discr_from_dd(from_dd), to_discr=self._modal_discr(from_dd.domain_tag)) # }}} # {{{ boundary @memoize_method def _boundary_connection(self, boundary_tag): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), boundary_tag=boundary_tag) # }}} # {{{ interior faces @memoize_method def _interior_faces_connection(self): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), FACE_RESTR_INTERIOR, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) @memoize_method def opposite_face_connection(self): from meshmode.discretization.connection import \ make_opposite_face_connection return make_opposite_face_connection(self._setup_actx, self._interior_faces_connection()) # }}} # {{{ all-faces @memoize_method def _all_faces_volume_connection(self): return make_face_restriction( self._setup_actx, self._volume_discr, self.group_factory_for_discretization_tag(DISCR_TAG_BASE), FACE_RESTR_ALL, # FIXME: This will need to change as soon as we support # pyramids or other elements with non-identical face # types. per_face_groups=False) # }}} @property def dim(self): return self._volume_discr.dim @property def ambient_dim(self): return self._volume_discr.ambient_dim @property def real_dtype(self): return self._volume_discr.real_dtype @property def complex_dtype(self): return self._volume_discr.complex_dtype @property def mesh(self): return self._volume_discr.mesh def empty(self, array_context: ArrayContext, dtype=None): return self._volume_discr.empty(array_context, dtype) def zeros(self, array_context: ArrayContext, dtype=None): return self._volume_discr.zeros(array_context, dtype) def is_volume_where(self, where): return where is None or as_dofdesc(where).is_volume() @property def order(self): warn( "DiscretizationCollection.order is deprecated, " "consider using the orders of element groups instead. " "'order' will go away in 2021.", DeprecationWarning, stacklevel=2) from pytools import single_valued return single_valued(egrp.order for egrp in self._volume_discr.groups)