def _prepare_auto_where(auto_where, places=None): """ :arg auto_where: a 2-tuple, single identifier or `None` used as a hint to determine the default geometries. :arg places: a :class:`GeometryCollection`, whose :attr:`GeometryCollection.auto_where` is used by default if provided and `auto_where` is `None`. :return: a tuple ``(source, target)`` of :class:`~pytential.symbolic.primitives.DOFDescriptor`s denoting the default source and target geometries. """ if auto_where is None: if places is None: auto_source = sym.DEFAULT_SOURCE auto_target = sym.DEFAULT_TARGET else: auto_source, auto_target = places.auto_where elif isinstance(auto_where, (list, tuple)): auto_source, auto_target = auto_where else: auto_source = auto_where auto_target = auto_source return (sym.as_dofdesc(auto_source), sym.as_dofdesc(auto_target))
def test_interpolation(actx_factory, name, source_discr_stage, target_granularity): actx = actx_factory() nelements = 32 target_order = 7 qbx_order = 4 where = sym.as_dofdesc("test_interpolation") from_dd = sym.DOFDescriptor( geometry=where.geometry, discr_stage=source_discr_stage, granularity=sym.GRANULARITY_NODE) to_dd = sym.DOFDescriptor( geometry=where.geometry, discr_stage=sym.QBX_SOURCE_QUAD_STAGE2, granularity=target_granularity) mesh = mgen.make_curve_mesh(mgen.starfish, np.linspace(0.0, 1.0, nelements + 1), target_order) discr = Discretization(actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order)) from pytential.qbx import QBXLayerPotentialSource qbx = QBXLayerPotentialSource(discr, fine_order=4 * target_order, qbx_order=qbx_order, fmm_order=False) from pytential import GeometryCollection places = GeometryCollection(qbx, auto_where=where) sigma_sym = sym.var("sigma") op_sym = sym.sin(sym.interp(from_dd, to_dd, sigma_sym)) bound_op = bind(places, op_sym, auto_where=where) def discr_and_nodes(stage): density_discr = places.get_discretization(where.geometry, stage) return density_discr, actx.to_numpy( flatten(density_discr.nodes(), actx) ).reshape(density_discr.ambient_dim, -1) _, target_nodes = discr_and_nodes(sym.QBX_SOURCE_QUAD_STAGE2) source_discr, source_nodes = discr_and_nodes(source_discr_stage) sigma_target = np.sin(la.norm(target_nodes, axis=0)) sigma_dev = unflatten( thaw(source_discr.nodes()[0], actx), actx.from_numpy(la.norm(source_nodes, axis=0)), actx) sigma_target_interp = actx.to_numpy( flatten(bound_op(actx, sigma=sigma_dev), actx) ) if name in ("default", "default_explicit", "stage2", "quad"): error = la.norm(sigma_target_interp - sigma_target) / la.norm(sigma_target) assert error < 1.0e-10 elif name in ("stage2_center",): assert len(sigma_target_interp) == 2 * len(sigma_target) else: raise ValueError(f"unknown test case name: {name}")
def __call__(self, actx: PyOpenCLArrayContext, source_dd, indices: BlockIndexRanges, **kwargs) -> BlockProxyPoints: from pytential import bind, sym source_dd = sym.as_dofdesc(source_dd) radii = bind(self.places, sym.expansion_radii(self.ambient_dim, dofdesc=source_dd))(actx) center_int = bind( self.places, sym.expansion_centers(self.ambient_dim, -1, dofdesc=source_dd))(actx) center_ext = bind( self.places, sym.expansion_centers(self.ambient_dim, +1, dofdesc=source_dd))(actx) return super().__call__(actx, source_dd, indices, expansion_radii=flatten(radii, actx), center_int=flatten(center_int, actx, leaf_class=DOFArray), center_ext=flatten(center_ext, actx, leaf_class=DOFArray), **kwargs)
def __init__(self, places, source_dd, code_getter, target_discrs_and_qbx_sides, target_association_tolerance, tree_kind, debug=None): """ .. rubric:: Constructor arguments See the attributes of the same name for the meaning of most of the constructor arguments. :arg tree_kind: The tree kind to pass to the tree builder :arg debug: a :class:`bool` flag for whether to enable potentially costly self-checks """ from pytential import sym self.places = places self.source_dd = sym.as_dofdesc(source_dd) self.lpot_source = places.get_geometry(self.source_dd.geometry) self.code_getter = code_getter self.target_discrs_and_qbx_sides = target_discrs_and_qbx_sides self.target_association_tolerance = target_association_tolerance self.tree_kind = tree_kind self.debug = self.lpot_source.debug if debug is None else debug
def get_discretization(self, dofdesc): """ :arg dofdesc: a :class:`~pytential.symbolic.primitives.DOFDescriptor` specifying the desired discretization. :return: a geometry object in the collection corresponding to the key *dofdesc*. If it is a :class:`~pytential.source.LayerPotentialSourceBase`, we look for the corresponding :class:`~meshmode.discretization.Discretization` in its attributes instead. """ dofdesc = sym.as_dofdesc(dofdesc) if dofdesc.geometry in self.places: discr = self.places[dofdesc.geometry] else: raise KeyError('geometry not in the collection: {}'.format( dofdesc.geometry)) from pytential.qbx import QBXLayerPotentialSource from pytential.source import LayerPotentialSourceBase if isinstance(discr, QBXLayerPotentialSource): return self._get_lpot_discretization(discr, dofdesc) elif isinstance(discr, LayerPotentialSourceBase): return discr.density_discr else: return discr
def refine_geometry_collection(places, group_factory=None, refine_discr_stage=None, kernel_length_scale=None, force_stage2_uniform_refinement_rounds=None, scaled_max_curvature_threshold=None, expansion_disturbance_tolerance=None, maxiter=None, debug=None, visualize=False): """Entry point for refining all the :class:`~pytential.qbx.QBXLayerPotentialSource` in the given collection. The :class:`~pytential.symbolic.execution.GeometryCollection` performs on-demand refinement, but this function can be used to tweak the parameters. :arg places: A :class:`~pytential.symbolic.execution.GeometryCollection`. :arg refine_discr_stage: Defines up to which stage the refinement should be performed. One of :class:`~pytential.symbolic.primitives.QBX_SOURCE_STAGE1`, :class:`~pytential.symbolic.primitives.QBX_SOURCE_STAGE2` or :class:`~pytential.symbolic.primitives.QBX_SOURCE_QUAD_STAGE2`. :arg group_factory: An instance of :class:`meshmode.mesh.discretization.ElementGroupFactory`. Used for discretizing the coarse refined mesh. :arg kernel_length_scale: The kernel length scale, or *None* if not applicable. All panels are refined to below this size. :arg maxiter: The maximum number of refiner iterations. """ from pytential import sym if refine_discr_stage is None: if force_stage2_uniform_refinement_rounds is not None: refine_discr_stage = sym.QBX_SOURCE_STAGE2 else: refine_discr_stage = sym.QBX_SOURCE_STAGE1 from pytential.qbx import QBXLayerPotentialSource places = places.copy() for geometry in places.places: dofdesc = sym.as_dofdesc(geometry).copy( discr_stage=refine_discr_stage) lpot_source = places.get_geometry(dofdesc.geometry) if not isinstance(lpot_source, QBXLayerPotentialSource): continue _refine_for_global_qbx(places, dofdesc, lpot_source.refiner_code_container.get_wrangler(), group_factory=group_factory, kernel_length_scale=kernel_length_scale, scaled_max_curvature_threshold=scaled_max_curvature_threshold, expansion_disturbance_tolerance=expansion_disturbance_tolerance, force_stage2_uniform_refinement_rounds=( force_stage2_uniform_refinement_rounds), maxiter=maxiter, debug=debug, visualize=visualize, _copy_collection=False) return places
def check_sufficient_source_quadrature_resolution(self, stage2_density_discr, tree, peer_lists, refine_flags, debug, wait_for=None): actx = self.array_context # Avoid generating too many kernels. from pytools import div_ceil max_levels = MAX_LEVELS_INCREMENT * div_ceil(tree.nlevels, MAX_LEVELS_INCREMENT) knl = self.code_container.sufficient_source_quadrature_resolution_checker( tree.dimensions, tree.coord_dtype, tree.box_id_dtype, peer_lists.peer_list_starts.dtype, tree.particle_id_dtype, max_levels) if debug: nelements_to_refine_prev = actx.to_numpy( actx.np.sum(refine_flags)).item() found_element_to_refine = actx.zeros(1, dtype=np.int32) found_element_to_refine.finish() from pytential import bind, sym dd = sym.as_dofdesc(sym.GRANULARITY_ELEMENT).to_stage2() source_danger_zone_radii_by_element = flatten( bind( stage2_density_discr, sym._source_danger_zone_radii(stage2_density_discr.ambient_dim, dofdesc=dd))(self.array_context), self.array_context) unwrap_args = AreaQueryElementwiseTemplate.unwrap_args evt = knl(*unwrap_args( tree, peer_lists, tree.box_to_qbx_center_starts, tree.box_to_qbx_center_lists, tree.qbx_element_to_source_starts, tree.qbx_user_source_slice.start, tree.qbx_user_center_slice.start, tree.sorted_target_ids, source_danger_zone_radii_by_element, tree.nqbxelements, refine_flags, found_element_to_refine, *tree.sources), range=slice(tree.nqbxsources), queue=actx.queue, wait_for=wait_for) import pyopencl as cl cl.wait_for_events([evt]) if debug: nelements_to_refine = actx.to_numpy( actx.np.sum(refine_flags)).item() if nelements_to_refine > nelements_to_refine_prev: logger.debug("refiner: found %d element(s) to refine", nelements_to_refine - nelements_to_refine_prev) return actx.to_numpy(found_element_to_refine)[0] == 1
def _prepare_domains(nresults, places, domains, default_domain): """ :arg nresults: number of results. :arg places: a :class:`~pytential.symbolic.execution.GeometryCollection`. :arg domains: recommended domains. :arg default_domain: default value for domains which are not provided. :return: a list of domains for each result. If domains is `None`, each element in the list is *default_domain*. If *domains* is a scalar (i.e., not a *list* or *tuple*), each element in the list is *domains*. Otherwise, *domains* is returned as is. """ if domains is None: dom_name = default_domain return nresults * [dom_name] elif not isinstance(domains, (list, tuple)): dom_name = domains return nresults * [dom_name] domains = [sym.as_dofdesc(d) for d in domains] assert len(domains) == nresults return domains
def build_tree_with_qbx_metadata(actx: PyOpenCLArrayContext, places, tree_builder, particle_list_filter, sources_list=(), targets_list=(), use_stage2_discr=False): """Return a :class:`TreeWithQBXMetadata` built from the given layer potential source. This contains particles of four different types: * source particles either from :class:`~pytential.symbolic.primitives.QBX_SOURCE_STAGE1` or :class:`~pytential.symbolic.primitives.QBX_SOURCE_QUAD_STAGE2`. * centers from :class:`~pytential.symbolic.primitives.QBX_SOURCE_STAGE1`. * targets from ``targets_list``. :arg actx: A :class:`PyOpenCLArrayContext` :arg places: An instance of :class:`~pytential.symbolic.execution.GeometryCollection`. :arg targets_list: A list of :class:`pytential.target.TargetBase` :arg use_stage2_discr: If *True*, builds a tree with stage 2 sources. If *False*, the tree is built with stage 1 sources. """ # The ordering of particles is as follows: # - sources go first # - then centers # - then targets from pytential import bind, sym stage1_density_discrs = [] density_discrs = [] for source_name in sources_list: dd = sym.as_dofdesc(source_name) discr = places.get_discretization(dd.geometry) stage1_density_discrs.append(discr) if use_stage2_discr: discr = places.get_discretization(dd.geometry, sym.QBX_SOURCE_QUAD_STAGE2) density_discrs.append(discr) # TODO: update code to work for multiple source discretizations if len(sources_list) != 1: raise RuntimeError("can only build a tree for a single source") def _make_centers(discr): return bind(discr, sym.interleaved_expansion_centers(discr.ambient_dim))(actx) stage1_density_discr = stage1_density_discrs[0] density_discr = density_discrs[0] from meshmode.dof_array import flatten, thaw from pytential.utils import flatten_if_needed sources = flatten(thaw(actx, density_discr.nodes())) centers = flatten(_make_centers(stage1_density_discr)) targets = [flatten_if_needed(actx, tgt.nodes()) for tgt in targets_list] queue = actx.queue particles = tuple( cl.array.concatenate(dim_coords, queue=queue) for dim_coords in zip(sources, centers, *targets)) # Counts nparticles = len(particles[0]) npanels = density_discr.mesh.nelements nsources = len(sources[0]) ncenters = len(centers[0]) # Each source gets an interior / exterior center. assert 2 * nsources == ncenters or use_stage2_discr ntargets = sum(tgt.ndofs for tgt in targets_list) # Slices qbx_user_source_slice = slice(0, nsources) center_slice_start = nsources qbx_user_center_slice = slice(center_slice_start, center_slice_start + ncenters) panel_slice_start = center_slice_start + ncenters target_slice_start = panel_slice_start qbx_user_target_slice = slice(target_slice_start, target_slice_start + ntargets) # Build tree with sources and centers. Split boxes # only because of sources. refine_weights = cl.array.zeros(queue, nparticles, np.int32) refine_weights[:nsources].fill(1) refine_weights.finish() tree, evt = tree_builder(queue, particles, max_leaf_refine_weight=MAX_REFINE_WEIGHT, refine_weights=refine_weights) # Compute box => particle class relations flags = refine_weights del refine_weights particle_classes = {} for class_name, particle_slice, fixup in (("box_to_qbx_source", qbx_user_source_slice, 0), ("box_to_qbx_target", qbx_user_target_slice, -target_slice_start), ("box_to_qbx_center", qbx_user_center_slice, -center_slice_start)): flags.fill(0) flags[particle_slice].fill(1) flags.finish() box_to_class = (particle_list_filter.filter_target_lists_in_user_order( queue, tree, flags).with_queue(actx.queue)) if fixup: box_to_class.target_lists += fixup particle_classes[class_name + "_starts"] = box_to_class.target_starts particle_classes[class_name + "_lists"] = box_to_class.target_lists del flags del box_to_class # Compute panel => source relation qbx_panel_to_source_starts = cl.array.empty(queue, npanels + 1, dtype=tree.particle_id_dtype) el_offset = 0 node_nr_base = 0 for group in density_discr.groups: qbx_panel_to_source_starts[el_offset:el_offset + group.nelements] = \ cl.array.arange(queue, node_nr_base, node_nr_base + group.ndofs, group.nunit_dofs, dtype=tree.particle_id_dtype) node_nr_base += group.ndofs el_offset += group.nelements qbx_panel_to_source_starts[-1] = nsources # Compute panel => center relation qbx_panel_to_center_starts = (2 * qbx_panel_to_source_starts if not use_stage2_discr else None) # Transfer all tree attributes. tree_attrs = {} for attr_name in tree.__class__.fields: try: tree_attrs[attr_name] = getattr(tree, attr_name) except AttributeError: pass tree_attrs.update(particle_classes) return TreeWithQBXMetadata( qbx_panel_to_source_starts=qbx_panel_to_source_starts, qbx_panel_to_center_starts=qbx_panel_to_center_starts, qbx_user_source_slice=qbx_user_source_slice, qbx_user_center_slice=qbx_user_center_slice, qbx_user_target_slice=qbx_user_target_slice, nqbxpanels=npanels, nqbxsources=nsources, nqbxcenters=ncenters, nqbxtargets=ntargets, **tree_attrs).with_queue(None)
def __call__(self, actx: PyOpenCLArrayContext, source_dd, indices: BlockIndexRanges, **kwargs) -> BlockProxyPoints: """Generate proxy points for each block in *indices* with nodes in the discretization *source_dd*. :arg source_dd: a :class:`~pytential.symbolic.primitives.DOFDescriptor` for the discretization on which the proxy points are to be generated. """ from pytential import sym source_dd = sym.as_dofdesc(source_dd) discr = self.places.get_discretization(source_dd.geometry, source_dd.discr_stage) # {{{ get proxy centers and radii sources = flatten(discr.nodes(), actx, leaf_class=DOFArray) knl = self.get_centers_knl(actx) _, (centers_dev, ) = knl(actx.queue, sources=sources, srcindices=indices.indices, srcranges=indices.ranges) knl = self.get_radii_knl(actx) _, (radii_dev, ) = knl(actx.queue, sources=sources, srcindices=indices.indices, srcranges=indices.ranges, radius_factor=self.radius_factor, proxy_centers=centers_dev, **kwargs) # }}} # {{{ build proxy points for each block from arraycontext import to_numpy centers = np.vstack(to_numpy(centers_dev, actx)) radii = to_numpy(radii_dev, actx) nproxy = self.nproxy * indices.nblocks proxies = np.empty((self.ambient_dim, nproxy), dtype=centers.dtype) pxy_nr_base = 0 for i in range(indices.nblocks): points = radii[i] * self.ref_points + centers[:, i].reshape(-1, 1) proxies[:, pxy_nr_base:pxy_nr_base + self.nproxy] = points pxy_nr_base += self.nproxy # }}} pxyindices = np.arange(0, nproxy, dtype=indices.indices.dtype) pxyranges = np.arange(0, nproxy + 1, self.nproxy) from arraycontext import freeze, from_numpy from pytential.linalg import make_block_index_from_array return BlockProxyPoints( lpot_source=self.places.get_geometry(source_dd.geometry), srcindices=indices, indices=make_block_index_from_array(pxyindices, pxyranges), points=freeze(from_numpy(proxies, actx), actx), centers=freeze(centers_dev, actx), radii=freeze(radii_dev, actx), )
def _refine_for_global_qbx(places, dofdesc, wrangler, group_factory=None, kernel_length_scale=None, force_stage2_uniform_refinement_rounds=None, scaled_max_curvature_threshold=None, expansion_disturbance_tolerance=None, maxiter=None, debug=None, visualize=False, _copy_collection=False): """Entry point for calling the refiner. Once the refinement is complete, the refined discretizations can be obtained from *places* by calling :meth:`~pytential.GeometryCollection.get_discretization`. :returns: a new version of the :class:`pytential.GeometryCollection` *places* with (what)? Depending on *_copy_collection*, *places* is updated in-place or copied. """ from pytential import sym dofdesc = sym.as_dofdesc(dofdesc) from pytential.qbx import QBXLayerPotentialSource lpot_source = places.get_geometry(dofdesc.geometry) if not isinstance(lpot_source, QBXLayerPotentialSource): raise ValueError( f"'{dofdesc.geometry}' is not a QBXLayerPotentialSource") # {{{ if maxiter is None: maxiter = 10 if debug is None: # FIXME: Set debug=False by default once everything works. debug = lpot_source.debug if expansion_disturbance_tolerance is None: expansion_disturbance_tolerance = 0.025 if force_stage2_uniform_refinement_rounds is None: force_stage2_uniform_refinement_rounds = 0 if group_factory is None: from meshmode.discretization.poly_element import \ InterpolatoryQuadratureSimplexGroupFactory group_factory = InterpolatoryQuadratureSimplexGroupFactory( lpot_source.density_discr.groups[0].order) # }}} # {{{ # FIXME: would be nice if this was an IntFlag or something ordered stage_index_map = { sym.QBX_SOURCE_STAGE1: 1, sym.QBX_SOURCE_STAGE2: 2, sym.QBX_SOURCE_QUAD_STAGE2: 3 } if dofdesc.discr_stage not in stage_index_map: raise ValueError(f"unknown discr stage: {dofdesc.discr_stage}") stage_index = stage_index_map[dofdesc.discr_stage] geometry = dofdesc.geometry def add_to_cache(refine_discr, refine_conn, from_ds, to_ds): places._add_discr_to_cache(refine_discr, geometry, to_ds) places._add_conn_to_cache(refine_conn, geometry, from_ds, to_ds) def get_from_cache(from_ds, to_ds): discr = places._get_discr_from_cache(geometry, to_ds) conn = places._get_conn_from_cache(geometry, from_ds, to_ds) return discr, conn if _copy_collection: places = places.copy() # }}} # {{{ discr = lpot_source.density_discr if stage_index >= 1: ds = (None, sym.QBX_SOURCE_STAGE1) try: discr, conn = get_from_cache(*ds) except KeyError: discr, conn = _refine_qbx_stage1( lpot_source, discr, wrangler, group_factory, kernel_length_scale=kernel_length_scale, scaled_max_curvature_threshold=( scaled_max_curvature_threshold), expansion_disturbance_tolerance=( expansion_disturbance_tolerance), maxiter=maxiter, debug=debug, visualize=visualize) add_to_cache(discr, conn, *ds) if stage_index >= 2: ds = (sym.QBX_SOURCE_STAGE1, sym.QBX_SOURCE_STAGE2) try: discr, conn = get_from_cache(*ds) except KeyError: discr, conn = _refine_qbx_stage2( lpot_source, discr, wrangler, group_factory, expansion_disturbance_tolerance=( expansion_disturbance_tolerance), force_stage2_uniform_refinement_rounds=( force_stage2_uniform_refinement_rounds), maxiter=maxiter, debug=debug, visualize=visualize) add_to_cache(discr, conn, *ds) if stage_index >= 3: ds = (sym.QBX_SOURCE_STAGE2, sym.QBX_SOURCE_QUAD_STAGE2) try: discr, conn = get_from_cache(*ds) except KeyError: discr, conn = _refine_qbx_quad_stage2(lpot_source, discr) add_to_cache(discr, conn, *ds) # }}} return places
def __call__(self, actx, source_dd, indices, **kwargs): """Generate proxy points for each given range of source points in the discretization in *source_dd*. :arg actx: a :class:`~meshmode.array_context.ArrayContext`. :arg source_dd: a :class:`~pytential.symbolic.primitives.DOFDescriptor` for the discretization on which the proxy points are to be generated. :arg indices: a :class:`sumpy.tools.BlockIndexRanges`. :return: a tuple of ``(proxies, pxyranges, pxycenters, pxyranges)``, where each element is a :class:`pyopencl.array.Array`. The sizes of the arrays are as follows: ``pxycenters`` is of size ``(2, nranges)``, ``pxyradii`` is of size ``(nranges,)``, ``pxyranges`` is of size ``(nranges + 1,)`` and ``proxies`` is of size ``(2, nranges * nproxy)``. The proxy points in a range :math:`i` can be obtained by a slice ``proxies[pxyranges[i]:pxyranges[i + 1]]`` and are all at a distance ``pxyradii[i]`` from the range center ``pxycenters[i]``. """ def _affine_map(v, A, b): return np.dot(A, v) + b from pytential import bind, sym source_dd = sym.as_dofdesc(source_dd) discr = self.places.get_discretization( source_dd.geometry, source_dd.discr_stage) radii = bind(self.places, sym.expansion_radii( self.ambient_dim, dofdesc=source_dd))(actx) center_int = bind(self.places, sym.expansion_centers( self.ambient_dim, -1, dofdesc=source_dd))(actx) center_ext = bind(self.places, sym.expansion_centers( self.ambient_dim, +1, dofdesc=source_dd))(actx) from meshmode.dof_array import flatten, thaw knl = self.get_kernel() _, (centers_dev, radii_dev,) = knl(actx.queue, sources=flatten(thaw(actx, discr.nodes())), center_int=flatten(center_int), center_ext=flatten(center_ext), expansion_radii=flatten(radii), srcindices=indices.indices, srcranges=indices.ranges, **kwargs) from pytential.utils import flatten_to_numpy centers = flatten_to_numpy(actx, centers_dev) radii = flatten_to_numpy(actx, radii_dev) proxies = np.empty(indices.nblocks, dtype=np.object) for i in range(indices.nblocks): proxies[i] = _affine_map(self.ref_points, A=(radii[i] * np.eye(self.ambient_dim)), b=centers[:, i].reshape(-1, 1)) pxyranges = actx.from_numpy(np.arange( 0, proxies.shape[0] * proxies[0].shape[1] + 1, proxies[0].shape[1], dtype=indices.ranges.dtype)) proxies = make_obj_array([ actx.freeze(actx.from_numpy(np.hstack([p[idim] for p in proxies]))) for idim in range(self.ambient_dim) ]) centers = make_obj_array([ actx.freeze(centers_dev[idim]) for idim in range(self.ambient_dim) ]) assert pxyranges[-1] == proxies[0].shape[0] return proxies, actx.freeze(pxyranges), centers, actx.freeze(radii_dev)
def connection_from_dds(places, from_dd, to_dd): """ :arg places: a :class:`~pytential.symbolic.execution.GeometryCollection` or an argument taken by its constructor. :arg from_dd: a descriptor for the incoming degrees of freedom. This can be a :class:`~pytential.symbolic.primitives.DOFDescriptor` or an identifier that can be transformed into one by :func:`~pytential.symbolic.primitives.as_dofdesc`. :arg to_dd: a descriptor for the outgoing degrees of freedom. :return: a :class:`DOFConnection` transporting between the two kinds of DOF vectors. """ from pytential import sym from_dd = sym.as_dofdesc(from_dd) to_dd = sym.as_dofdesc(to_dd) from pytential import GeometryCollection if not isinstance(places, GeometryCollection): places = GeometryCollection(places) lpot = places.get_geometry(from_dd.geometry) from_discr = places.get_discretization(from_dd.geometry, from_dd.discr_stage) to_discr = places.get_discretization(to_dd.geometry, to_dd.discr_stage) if from_dd.geometry != to_dd.geometry: raise ValueError("cannot interpolate between different geometries") if from_dd.granularity is not sym.GRANULARITY_NODE: raise ValueError("can only interpolate from `GRANULARITY_NODE`") connections = [] if from_dd.discr_stage is not to_dd.discr_stage: from pytential.qbx import QBXLayerPotentialSource if not isinstance(lpot, QBXLayerPotentialSource): raise ValueError("can only interpolate on a " "`QBXLayerPotentialSource`") if to_dd.discr_stage is not sym.QBX_SOURCE_QUAD_STAGE2: # TODO: can probably extend this to project from a QUAD_STAGE2 # using L2ProjectionInverseDiscretizationConnection raise ValueError("can only interpolate to " "`QBX_SOURCE_QUAD_STAGE2`") # FIXME: would be nice if these were ordered by themselves stage_name_to_index_map = { None: 0, sym.QBX_SOURCE_STAGE1: 1, sym.QBX_SOURCE_STAGE2: 2, sym.QBX_SOURCE_QUAD_STAGE2: 3 } stage_index_to_name_map = { i: name for name, i in stage_name_to_index_map.items() } from_stage = stage_name_to_index_map[from_dd.discr_stage] to_stage = stage_name_to_index_map[to_dd.discr_stage] for istage in range(from_stage, to_stage): conn = places._get_conn_from_cache( from_dd.geometry, stage_index_to_name_map[istage], stage_index_to_name_map[istage + 1]) connections.append(conn) if from_dd.granularity is not to_dd.granularity: if to_dd.granularity is sym.GRANULARITY_NODE: pass elif to_dd.granularity is sym.GRANULARITY_CENTER: connections.append(CenterGranularityConnection(to_discr)) elif to_dd.granularity is sym.GRANULARITY_ELEMENT: raise ValueError("Creating a connection to element granularity " "is not allowed. Use Elementwise{Max,Min,Sum}.") else: raise ValueError(f"invalid to_dd granularity: {to_dd.granularity}") if from_dd.granularity is not to_dd.granularity: conn = DOFConnection(connections, from_dd=from_dd, to_dd=to_dd) else: from meshmode.discretization.connection import \ ChainedDiscretizationConnection conn = ChainedDiscretizationConnection(connections, from_discr=from_discr) return conn
def connection_from_dds(places, from_dd, to_dd): """ :arg places: a :class:`~pytential.symbolic.execution.GeometryCollection` or an argument taken by its constructor. :arg from_dd: a descriptor for the incoming degrees of freedom. This can be a :class:`~pytential.symbolic.primitives.DOFDescriptor` or an identifier that can be transformed into one by :func:`~pytential.symbolic.primitives.as_dofdesc`. :arg to_dd: a descriptor for the outgoing degrees of freedom. :return: a :class:`DOFConnection` transporting between the two kinds of DOF vectors. """ from pytential import sym from_dd = sym.as_dofdesc(from_dd) to_dd = sym.as_dofdesc(to_dd) from pytential.symbolic.execution import GeometryCollection if not isinstance(places, GeometryCollection): places = GeometryCollection(places) from_discr = places.get_geometry(from_dd) if from_dd.geometry != to_dd.geometry: raise ValueError("cannot interpolate between different geometries") if from_dd.granularity is not sym.GRANULARITY_NODE: raise ValueError("can only interpolate from `GRANULARITY_NODE`") connections = [] if from_dd.discr_stage is not to_dd.discr_stage: from pytential.qbx import QBXLayerPotentialSource if not isinstance(from_discr, QBXLayerPotentialSource): raise ValueError("can only interpolate on a " "`QBXLayerPotentialSource`") if to_dd.discr_stage is not sym.QBX_SOURCE_QUAD_STAGE2: # TODO: can probably extend this to project from a QUAD_STAGE2 # using L2ProjectionInverseDiscretizationConnection raise ValueError("can only interpolate to " "`QBX_SOURCE_QUAD_STAGE2`") if from_dd.discr_stage is sym.QBX_SOURCE_QUAD_STAGE2: pass elif from_dd.discr_stage is sym.QBX_SOURCE_STAGE2: connections.append( from_discr.refined_interp_to_ovsmp_quad_connection) else: connections.append(from_discr.resampler) if from_dd.granularity is not to_dd.granularity: to_discr = places.get_discretization(to_dd) if to_dd.granularity is sym.GRANULARITY_NODE: pass elif to_dd.granularity is sym.GRANULARITY_CENTER: connections.append(CenterGranularityConnection(to_discr)) elif to_dd.granularity is sym.GRANULARITY_ELEMENT: raise ValueError("Creating a connection to element granularity " "is not allowed. Use Elementwise{Max,Min,Sum}.") else: raise ValueError("invalid to_dd granularity: %s" % to_dd.granularity) return DOFConnection(connections, from_dd=from_dd, to_dd=to_dd)
def get_geometry(self, dofdesc): dofdesc = sym.as_dofdesc(dofdesc) return self.places[dofdesc.geometry]
def __init__(self, places, auto_where=None): """ :arg places: a scalar, tuple of or mapping of symbolic names to geometry objects. Supported objects are :class:`~pytential.source.PotentialSource`, :class:`~potential.target.TargetBase` and :class:`~meshmode.discretization.Discretization`. :arg auto_where: location identifier for each geometry object, used to denote specific discretizations, e.g. in the case where *places* is a :class:`~pytential.source.LayerPotentialSourceBase`. By default, we assume :class:`~pytential.symbolic.primitives.DEFAULT_SOURCE` and :class:`~pytential.symbolic.primitives.DEFAULT_TARGET` for sources and targets, respectively. """ from pytential.target import TargetBase from pytential.source import PotentialSource from pytential.qbx import QBXLayerPotentialSource from meshmode.discretization import Discretization # {{{ define default source and target descriptors if isinstance(auto_where, (list, tuple)): auto_source, auto_target = auto_where else: auto_source, auto_target = auto_where, None if auto_source is None: auto_source = sym.DEFAULT_SOURCE if auto_target is None: auto_target = sym.DEFAULT_TARGET auto_source = sym.as_dofdesc(auto_source) auto_target = sym.as_dofdesc(auto_target) self.auto_where = (auto_source, auto_target) # }}} # {{{ construct dict self.places = {} if isinstance(places, QBXLayerPotentialSource): self.places[auto_source.geometry] = places self.places[auto_target.geometry] = \ self._get_lpot_discretization(places, auto_target) elif isinstance(places, (Discretization, PotentialSource)): self.places[auto_source.geometry] = places self.places[auto_target.geometry] = places elif isinstance(places, TargetBase): self.places[auto_target.geometry] = places elif isinstance(places, tuple): source_discr, target_discr = places self.places[auto_source.geometry] = source_discr self.places[auto_target.geometry] = target_discr else: self.places = places.copy() for p in six.itervalues(self.places): if not isinstance(p, (PotentialSource, TargetBase, Discretization)): raise TypeError("Must pass discretization, targets or " "layer potential sources as 'places'.") # }}} self.caches = {}
def associate_targets_to_qbx_centers(places, geometry, wrangler, target_discrs_and_qbx_sides, target_association_tolerance, debug=True, wait_for=None): """ Associate targets to centers in a layer potential source. :arg places: A :class:`~pytential.GeometryCollection`. :arg geometry: Name of the source geometry in *places* for which to associate targets. :arg wrangler: An instance of :class:`TargetAssociationWrangler` :arg target_discrs_and_qbx_sides: a list of tuples ``(discr, sides)``, where *discr* is a :class:`meshmode.discretization.Discretization` or a :class:`pytential.target.TargetBase` instance, and *sides* is either a :class:`int` or an array of (:class:`numpy.int8`) side requests for each target. The side request can take on the values in :ref:`qbx-side-request-table`. :raises pytential.qbx.QBXTargetAssociationFailedException: when target association failed to find a center for a target. The returned exception object contains suggested refine flags. :returns: A :class:`QBXTargetAssociation`. """ from pytential import sym dofdesc = sym.as_dofdesc(geometry).to_stage1() tree = wrangler.build_tree( places, sources_list=[dofdesc.geometry], targets_list=[discr for discr, _ in target_discrs_and_qbx_sides]) peer_lists = wrangler.find_peer_lists(tree) target_status = cl.array.zeros(wrangler.queue, tree.nqbxtargets, dtype=np.int32) target_status.finish() have_close_targets = wrangler.mark_targets(places, dofdesc, tree, peer_lists, target_status, debug) target_assoc = wrangler.make_default_target_association(tree.nqbxtargets) if not have_close_targets: return target_assoc.with_queue(None) target_flags = wrangler.make_target_flags(target_discrs_and_qbx_sides) wrangler.find_centers(places, dofdesc, tree, peer_lists, target_status, target_flags, target_assoc, target_association_tolerance, debug) center_not_found = ( target_status == target_status_enum.MARKED_QBX_CENTER_PENDING) if center_not_found.any().get(): surface_target = ( (target_flags == target_flag_enum.INTERIOR_SURFACE_TARGET) | (target_flags == target_flag_enum.EXTERIOR_SURFACE_TARGET)) if (center_not_found & surface_target).any().get(): fail_msg = "An on-surface target was not assigned a QBX center." else: fail_msg = "Some targets were not assigned a QBX center." fail_msg += (" Make sure to check the values you are passing " "for qbx_forced_limit on your symbolic layer potential " "operators. Those (or their default values) may " "constrain center choice to on-or-near surface " "sides of the geometry in a way that causes this issue.") fail_msg += (" As a last resort, you can try increasing " "the 'target_association_tolerance' parameter, but " "this could also cause an invalid center assignment.") refine_flags = cl.array.zeros(wrangler.queue, tree.nqbxpanels, dtype=np.int32) have_panel_to_refine = wrangler.mark_panels_for_refinement( places, dofdesc, tree, peer_lists, target_status, refine_flags, debug) assert have_panel_to_refine raise QBXTargetAssociationFailedException( refine_flags=refine_flags.with_queue(None), failed_target_flags=center_not_found.with_queue(None), message=fail_msg) return target_assoc.with_queue(None)