Ejemplo n.º 1
0
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))
Ejemplo n.º 2
0
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}")
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
    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),
        )
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
    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)
Ejemplo n.º 13
0
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
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
 def get_geometry(self, dofdesc):
     dofdesc = sym.as_dofdesc(dofdesc)
     return self.places[dofdesc.geometry]
Ejemplo n.º 16
0
    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 = {}
Ejemplo n.º 17
0
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)