Beispiel #1
0
    def check_expansion_disks_undisturbed_by_sources(
            self,
            stage1_density_discr,
            tree,
            peer_lists,
            expansion_disturbance_tolerance,
            refine_flags,
            debug,
            wait_for=None):

        # 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.expansion_disk_undisturbed_by_sources_checker(
            tree.dimensions, tree.coord_dtype, tree.box_id_dtype,
            peer_lists.peer_list_starts.dtype, tree.particle_id_dtype,
            max_levels)

        if debug:
            npanels_to_refine_prev = cl.array.sum(refine_flags).get()

        found_panel_to_refine = cl.array.zeros(self.queue, 1, np.int32)
        found_panel_to_refine.finish()
        unwrap_args = AreaQueryElementwiseTemplate.unwrap_args

        from pytential import bind, sym
        center_danger_zone_radii = flatten(
            bind(
                stage1_density_discr,
                sym.expansion_radii(stage1_density_discr.ambient_dim,
                                    granularity=sym.GRANULARITY_CENTER))(
                                        self.array_context))

        evt = knl(*unwrap_args(
            tree, peer_lists, tree.box_to_qbx_source_starts,
            tree.box_to_qbx_source_lists, tree.qbx_panel_to_source_starts,
            tree.qbx_panel_to_center_starts, tree.qbx_user_source_slice.start,
            tree.qbx_user_center_slice.start, tree.sorted_target_ids,
            center_danger_zone_radii, expansion_disturbance_tolerance,
            tree.nqbxpanels, refine_flags, found_panel_to_refine,
            *tree.sources),
                  range=slice(tree.nqbxcenters),
                  queue=self.queue,
                  wait_for=wait_for)

        cl.wait_for_events([evt])

        if debug:
            npanels_to_refine = cl.array.sum(refine_flags).get()
            if npanels_to_refine > npanels_to_refine_prev:
                logger.debug("refiner: found {} panel(s) to refine".format(
                    npanels_to_refine - npanels_to_refine_prev))

        return found_panel_to_refine.get()[0] == 1
Beispiel #2
0
    def map_int_g(self, expr):
        source_discr = self.places.get_discretization(expr.source.geometry,
                                                      expr.source.discr_stage)
        target_discr = self.places.get_discretization(expr.target.geometry,
                                                      expr.target.discr_stage)

        if source_discr is not target_discr:
            raise NotImplementedError

        rec_density = self._blk_mapper.rec(expr.density)
        if is_zero(rec_density):
            return 0

        if not np.isscalar(rec_density):
            raise NotImplementedError

        # NOTE: copied from pytential.symbolic.primitives.IntG
        # NOTE: P2P evaluation only uses the inner kernel, so it should not
        # get other kernel_args, e.g. normal vectors in a double layer
        kernel = expr.kernel.get_base_kernel()
        kernel_args = kernel.get_args() + kernel.get_source_args()
        kernel_args = set(arg.loopy_arg.name for arg in kernel_args)

        actx = self.array_context
        kernel_args = _get_layer_potential_args(self._mat_mapper,
                                                expr,
                                                include_args=kernel_args)
        if self.exclude_self:
            kernel_args["target_to_source"] = actx.from_numpy(
                np.arange(0, target_discr.ndofs, dtype=np.int))

        from sumpy.p2p import P2PMatrixBlockGenerator
        mat_gen = P2PMatrixBlockGenerator(actx.context, (kernel, ),
                                          exclude_self=self.exclude_self)

        from meshmode.dof_array import flatten, thaw
        _, (mat, ) = mat_gen(actx.queue,
                             targets=flatten(thaw(actx, target_discr.nodes())),
                             sources=flatten(thaw(actx, source_discr.nodes())),
                             index_set=self.index_set,
                             **kernel_args)

        return rec_density * actx.to_numpy(mat)
Beispiel #3
0
def dof_array_to_numpy(actx, ary):
    """Converts DOFArrays (or object arrays of DOFArrays) to NumPy arrays.
    Object arrays get turned into multidimensional arrays.
    """
    from pytools.obj_array import obj_array_vectorize
    from meshmode.dof_array import flatten
    arr = obj_array_vectorize(actx.to_numpy, flatten(ary))
    if arr.dtype.char == "O":
        arr = np.array(list(arr))
    return arr
Beispiel #4
0
    def my_checkpoint(step, t, dt, state):

        write_restart = (check_step(step, nrestart)
                         if step != restart_step else False)
        if write_restart is True:
            with open(snapshot_pattern.format(step=step, rank=rank),
                      "wb") as f:
                pickle.dump(
                    {
                        "local_mesh": local_mesh,
                        "state": obj_array_vectorize(actx.to_numpy,
                                                     flatten(state)),
                        "t": t,
                        "step": step,
                        "global_nelements": global_nelements,
                        "num_parts": nparts,
                    }, f)

        def loc_fn(t):
            return flame_start_loc + flame_speed * t

        exact_soln = PlanarDiscontinuity(dim=dim,
                                         disc_location=loc_fn,
                                         sigma=0.0000001,
                                         nspecies=nspecies,
                                         temperature_left=temp_ignition,
                                         temperature_right=temp_unburned,
                                         pressure_left=pres_burned,
                                         pressure_right=pres_unburned,
                                         velocity_left=vel_burned,
                                         velocity_right=vel_unburned,
                                         species_mass_left=y_burned,
                                         species_mass_right=y_unburned)

        cv = split_conserved(dim, state)
        reaction_rates = eos.get_production_rates(cv)
        viz_fields = [("reaction_rates", reaction_rates)]

        return sim_checkpoint(discr=discr,
                              visualizer=visualizer,
                              eos=eos,
                              q=state,
                              vizname=casename,
                              step=step,
                              t=t,
                              dt=dt,
                              nstatus=nstatus,
                              nviz=nviz,
                              exittol=exittol,
                              constant_cfl=constant_cfl,
                              comm=comm,
                              vis_timer=vis_timer,
                              overwrite=True,
                              exact_soln=exact_soln,
                              viz_fields=viz_fields)
Beispiel #5
0
    def map_int_g(self, expr):
        source_discr = self.places.get_discretization(expr.source.geometry,
                                                      expr.source.discr_stage)
        target_discr = self.places.get_discretization(expr.target.geometry,
                                                      expr.target.discr_stage)

        result = 0
        for density, kernel in zip(expr.densities, expr.source_kernels):
            rec_density = self.rec(density)
            if is_zero(rec_density):
                continue

            assert isinstance(rec_density, np.ndarray)
            if not self.is_kind_matrix(rec_density):
                raise NotImplementedError("layer potentials on non-variables")

            # NOTE: copied from pytential.symbolic.primitives.IntG
            kernel_args = kernel.get_args() + kernel.get_source_args()
            kernel_args = {arg.loopy_arg.name for arg in kernel_args}

            actx = self.array_context
            kernel_args = _get_layer_potential_args(self,
                                                    expr,
                                                    include_args=kernel_args)
            if self.exclude_self:
                kernel_args["target_to_source"] = actx.from_numpy(
                    np.arange(0, target_discr.ndofs, dtype=np.int64))

            from sumpy.p2p import P2PMatrixGenerator
            mat_gen = P2PMatrixGenerator(actx.context, (kernel, ),
                                         exclude_self=self.exclude_self)

            from meshmode.dof_array import flatten, thaw
            _, (mat, ) = mat_gen(
                actx.queue,
                targets=flatten(thaw(actx, target_discr.nodes())),
                sources=flatten(thaw(actx, source_discr.nodes())),
                **kernel_args)

            result += actx.to_numpy(mat).dot(rec_density)

        return result
Beispiel #6
0
def partition_by_nodes(actx, discr,
        tree_kind="adaptive-level-restricted", max_nodes_in_box=None):
    """Generate equally sized ranges of nodes. The partition is created at the
    lowest level of granularity, i.e. nodes. This results in balanced ranges
    of points, but will split elements across different ranges.

    :arg discr: a :class:`meshmode.discretization.Discretization`.
    :arg tree_kind: if not *None*, it is passed to :class:`boxtree.TreeBuilder`.
    :arg max_nodes_in_box: passed to :class:`boxtree.TreeBuilder`.

    :return: a :class:`sumpy.tools.BlockIndexRanges`.
    """

    if max_nodes_in_box is None:
        # FIXME: this is just an arbitrary value
        max_nodes_in_box = 32

    if tree_kind is not None:
        from boxtree import box_flags_enum
        from boxtree import TreeBuilder

        builder = TreeBuilder(actx.context)

        from meshmode.dof_array import flatten, thaw
        tree, _ = builder(actx.queue,
                flatten(thaw(actx, discr.nodes())),
                max_particles_in_box=max_nodes_in_box,
                kind=tree_kind)

        tree = tree.get(actx.queue)
        leaf_boxes, = (tree.box_flags
                       & box_flags_enum.HAS_CHILDREN == 0).nonzero()

        indices = np.empty(len(leaf_boxes), dtype=np.object)
        for i, ibox in enumerate(leaf_boxes):
            box_start = tree.box_source_starts[ibox]
            box_end = box_start + tree.box_source_counts_cumul[ibox]
            indices[i] = tree.user_source_ids[box_start:box_end]

        ranges = actx.from_numpy(
                np.cumsum([0] + [box.shape[0] for box in indices])
                )
        indices = actx.from_numpy(np.hstack(indices))
    else:
        indices = actx.from_numpy(np.arange(0, discr.ndofs, dtype=np.int))
        ranges = actx.from_numpy(np.arange(
            0,
            discr.ndofs + 1,
            max_nodes_in_box, dtype=np.int))

    assert ranges[-1] == discr.ndofs

    return BlockIndexRanges(actx.context,
        actx.freeze(indices), actx.freeze(ranges))
Beispiel #7
0
    def exec_compute_potential_insn_direct(self, actx: PyOpenCLArrayContext,
            insn, bound_expr, evaluate):
        kernel_args = {}

        from pytential.utils import flatten_if_needed
        from meshmode.dof_array import flatten, thaw, unflatten

        for arg_name, arg_expr in insn.kernel_arguments.items():
            kernel_args[arg_name] = flatten_if_needed(actx, evaluate(arg_expr))

        from pytential import bind, sym
        waa = bind(bound_expr.places, sym.weights_and_area_elements(
            self.ambient_dim, dofdesc=insn.source))(actx)
        strengths = [waa * evaluate(density) for density in insn.densities]
        flat_strengths = [flatten(strength) for strength in strengths]

        results = []
        p2p = None

        for o in insn.outputs:
            target_discr = bound_expr.places.get_discretization(
                    o.target_name.geometry, o.target_name.discr_stage)

            if p2p is None:
                p2p = self.get_p2p(actx, source_kernels=insn.source_kernels,
                    target_kernels=insn.target_kernels)

            evt, output_for_each_kernel = p2p(actx.queue,
                    flatten_if_needed(actx, target_discr.nodes()),
                    flatten(thaw(actx, self.density_discr.nodes())),
                    flat_strengths, **kernel_args)

            from meshmode.discretization import Discretization
            result = output_for_each_kernel[o.target_kernel_index]
            if isinstance(target_discr, Discretization):
                result = unflatten(actx, target_discr, result)

            results.append((o.name, result))

        timing_data = {}
        return results, timing_data
Beispiel #8
0
def interpolate_to_meshmode(queue,
                            potential,
                            leaves_to_nodes_lookup,
                            order="tree"):
    """
    :arg potential: a DoF vector representing a field in :mod:`volumential`,
        in tree order.
    :arg leaves_to_nodes_lookup: a :class:`LeavesToNodesLookup`.
    :arg order: order of the input potential, either "tree" or "user".

    :returns: a :class:`pyopencl.Array` of shape (nnodes, 1) containing the
        interpolated data.
    """
    if order == "tree":
        potential_in_tree_order = True
    elif order == "user":
        potential_in_tree_order = False
    else:
        raise ValueError(f"order must be 'tree' or 'user' (got {order}).")

    arr_ctx = PyOpenCLArrayContext(queue)
    target_points = flatten(thaw(arr_ctx,
                                 leaves_to_nodes_lookup.discr.nodes()))

    traversal = leaves_to_nodes_lookup.trav
    tree = leaves_to_nodes_lookup.trav.tree

    dim = tree.dimensions

    # infer q_order from tree
    pts_per_box = tree.ntargets // traversal.ntarget_boxes
    assert pts_per_box * traversal.ntarget_boxes == tree.ntargets

    # allow for +/- 0.25 floating point error
    q_order = int(pts_per_box**(1 / dim) + 0.25)
    assert q_order**dim == pts_per_box

    interp_p = interpolate_volume_potential(
        target_points=target_points,
        traversal=traversal,
        wrangler=None,
        potential=potential,
        potential_in_tree_order=potential_in_tree_order,
        dim=dim,
        tree=tree,
        queue=queue,
        q_order=q_order,
        dtype=potential.dtype,
        lbl_lookup=None,
        balls_near_box_starts=leaves_to_nodes_lookup.nodes_in_leaf_starts,
        balls_near_box_lists=leaves_to_nodes_lookup.nodes_in_leaf_lists)

    return interp_p
Beispiel #9
0
def test_array_context_np_workalike(actx_factory):
    actx = actx_factory()

    from meshmode.mesh.generation import generate_regular_rect_mesh
    mesh = generate_regular_rect_mesh(
            a=(-0.5,)*2, b=(0.5,)*2, n=(8,)*2, order=3)

    discr = Discretization(actx, mesh, PolynomialWarpAndBlendGroupFactory(3))

    for sym_name, n_args in [
            ("sin", 1),
            ("exp", 1),
            ("arctan2", 2),
            ("minimum", 2),
            ("maximum", 2),
            ("where", 3),
            ("conj", 1),
            ]:
        args = [np.random.randn(discr.ndofs) for i in range(n_args)]
        ref_result = getattr(np, sym_name)(*args)

        # {{{ test DOFArrays

        actx_args = [unflatten(actx, discr, actx.from_numpy(arg)) for arg in args]

        actx_result = actx.to_numpy(
                flatten(getattr(actx.np, sym_name)(*actx_args)))

        assert np.allclose(actx_result, ref_result)

        # }}}

        # {{{ test object arrays of DOFArrays

        obj_array_args = [make_obj_array([arg]) for arg in actx_args]

        obj_array_result = actx.to_numpy(
                flatten(getattr(actx.np, sym_name)(*obj_array_args)[0]))

        assert np.allclose(obj_array_result, ref_result)
Beispiel #10
0
    def check_sufficient_source_quadrature_resolution(self,
                                                      stage2_density_discr,
                                                      tree,
                                                      peer_lists,
                                                      refine_flags,
                                                      debug,
                                                      wait_for=None):

        # 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:
            npanels_to_refine_prev = cl.array.sum(refine_flags).get()

        found_panel_to_refine = cl.array.zeros(self.queue, 1, np.int32)
        found_panel_to_refine.finish()

        from pytential import bind, sym
        dd = sym.as_dofdesc(sym.GRANULARITY_ELEMENT).to_stage2()
        source_danger_zone_radii_by_panel = flatten(
            bind(
                stage2_density_discr,
                sym._source_danger_zone_radii(stage2_density_discr.ambient_dim,
                                              dofdesc=dd))(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_panel_to_source_starts,
            tree.qbx_user_source_slice.start, tree.qbx_user_center_slice.start,
            tree.sorted_target_ids, source_danger_zone_radii_by_panel,
            tree.nqbxpanels, refine_flags, found_panel_to_refine,
            *tree.sources),
                  range=slice(tree.nqbxsources),
                  queue=self.queue,
                  wait_for=wait_for)

        cl.wait_for_events([evt])

        if debug:
            npanels_to_refine = cl.array.sum(refine_flags).get()
            if npanels_to_refine > npanels_to_refine_prev:
                logger.debug("refiner: found {} panel(s) to refine".format(
                    npanels_to_refine - npanels_to_refine_prev))

        return found_panel_to_refine.get()[0] == 1
Beispiel #11
0
    def flat_centers(self):
        """Return an object array of (interleaved) center coordinates.

        ``coord_t [ambient_dim][ncenters]``
        """
        from pytential import bind, sym

        centers = bind(
            self.places,
            sym.interleaved_expansion_centers(
                self.ambient_dim,
                dofdesc=self.source_dd.to_stage1()))(self.array_context)
        return obj_array_vectorize(self.array_context.freeze, flatten(centers))
Beispiel #12
0
def test_tangential_onb(ctx_factory):
    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue)

    from meshmode.mesh.generation import generate_torus
    mesh = generate_torus(5, 2, order=3)

    discr = Discretization(actx, mesh,
                           InterpolatoryQuadratureSimplexGroupFactory(3))

    tob = sym.tangential_onb(mesh.ambient_dim)
    nvecs = tob.shape[1]

    # make sure tangential_onb is mutually orthogonal and normalized
    orth_check = bind(
        discr,
        sym.make_obj_array([
            np.dot(tob[:, i], tob[:, j]) - (1 if i == j else 0)
            for i in range(nvecs) for j in range(nvecs)
        ]))(actx)

    from meshmode.dof_array import flatten
    orth_check = flatten(orth_check)
    for i, orth_i in enumerate(orth_check):
        assert (cl.clmath.fabs(orth_i) < 1e-13).get().all()

    # make sure tangential_onb is orthogonal to normal
    orth_check = bind(
        discr,
        sym.make_obj_array([
            np.dot(tob[:, i],
                   sym.normal(mesh.ambient_dim).as_vector())
            for i in range(nvecs)
        ]))(actx)

    orth_check = flatten(orth_check)
    for i, orth_i in enumerate(orth_check):
        assert (cl.clmath.fabs(orth_i) < 1e-13).get().all()
Beispiel #13
0
    def my_checkpoint(step, t, dt, state):

        write_restart = (check_step(step, nrestart)
                         if step != restart_step else False)
        if write_restart is True:
            with open(snapshot_pattern.format(step=step, rank=rank),
                      "wb") as f:
                pickle.dump(
                    {
                        "local_mesh": local_mesh,
                        "state": obj_array_vectorize(actx.to_numpy,
                                                     flatten(state)),
                        "t": t,
                        "step": step,
                        "global_nelements": global_nelements,
                        "num_parts": nparts,
                    }, f)

        uc = np.zeros(shape=(dim, ))
        uc[0] = mach * c_bkrnd

        #x0=f(time)
        exact_soln = Discontinuity(dim=dim,
                                   x0=.05,
                                   sigma=0.00001,
                                   rhol=rho2,
                                   rhor=rho1,
                                   pl=pressure2,
                                   pr=pressure1,
                                   ul=velocity2,
                                   ur=velocity1,
                                   uc=uc)

        return sim_checkpoint(discr=discr,
                              visualizer=visualizer,
                              eos=eos,
                              q=state,
                              vizname=casename,
                              step=step,
                              t=t,
                              dt=dt,
                              nstatus=nstatus,
                              nviz=nviz,
                              exittol=exittol,
                              constant_cfl=constant_cfl,
                              comm=comm,
                              vis_timer=vis_timer,
                              overwrite=True,
                              exact_soln=exact_soln,
                              sigma=sigma_sc,
                              kappa=kappa_sc)
Beispiel #14
0
def resample_to_numpy(conn, vec):
    if (isinstance(vec, np.ndarray) and vec.dtype.char == "O"
            and not isinstance(vec, DOFArray)):
        from pytools.obj_array import obj_array_vectorize
        return obj_array_vectorize(lambda x: resample_to_numpy(conn, x), vec)

    from numbers import Number
    if isinstance(vec, Number):
        nnodes = sum(grp.ndofs for grp in conn.to_discr.groups)
        return np.ones(nnodes) * vec
    else:
        resampled = conn(vec)
        actx = resampled.array_context
        return actx.to_numpy(flatten(resampled))
Beispiel #15
0
def flatten_if_needed(actx: PyOpenCLArrayContext, ary: np.ndarray):
    from pytools.obj_array import obj_array_vectorize_n_args
    from meshmode.dof_array import DOFArray, thaw, flatten

    if (isinstance(ary, np.ndarray) and ary.dtype.char == "O"
            and not isinstance(ary, DOFArray)):
        return obj_array_vectorize_n_args(flatten_if_needed, actx, ary)

    if not isinstance(ary, DOFArray):
        return ary

    if ary.array_context is None:
        ary = thaw(actx, ary)

    return flatten(ary)
Beispiel #16
0
    def flat_expansion_radii(self):
        """Return an array of radii associated with the (interleaved)
        expansion centers.

        ``coord_t [ncenters]``
        """
        from pytential import bind, sym

        radii = bind(
            self.places,
            sym.expansion_radii(self.ambient_dim,
                                granularity=sym.GRANULARITY_CENTER,
                                dofdesc=self.source_dd.to_stage1()))(
                                    self.array_context)

        return self.array_context.freeze(flatten(radii))
Beispiel #17
0
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)
Beispiel #18
0
    def __call__(self, queue, tol=1e-12, wait_for=None):
        """
        :arg queue: a :class:`pyopencl.CommandQueue`
        :tol: nodes close enough to the boundary will be treated as
            lying on the boundary, whose interpolated values are averaged.
        """
        arr_ctx = PyOpenCLArrayContext(queue)
        nodes = flatten(thaw(arr_ctx, self.discr.nodes()))
        radii = cl.array.zeros_like(nodes[0]) + tol

        lbl_lookup, evt = self.leaves_to_balls_lookup_builder(
            queue, self.trav.tree, nodes, radii, wait_for=wait_for)

        return LeavesToNodesLookup(
            trav=self.trav,
            discr=self.discr,
            nodes_in_leaf_starts=lbl_lookup.balls_near_box_starts,
            nodes_in_leaf_lists=lbl_lookup.balls_near_box_lists), evt
Beispiel #19
0
def resample_to_numpy(conn, vec, *, stack=False, by_group=False):
    """
    :arg stack: if *True* object arrays are stacked into a single
        :class:`~numpy.ndarray`.
    :arg by_group: if *True*, the per-group arrays in a :class:`DOFArray`
        are flattened separately. This can be used to write each group as a
        separate mesh (in supporting formats).
    """
    # "stack" exists as mainly as a workaround for Xdmf. See here:
    # https://github.com/inducer/pyvisfile/pull/12#discussion_r550959081
    # for (minimal) discussion.
    if isinstance(vec, np.ndarray) and vec.dtype.char == "O":
        from pytools.obj_array import obj_array_vectorize
        r = obj_array_vectorize(
                lambda x: resample_to_numpy(conn, x, by_group=by_group),
                vec)

        return _stack_object_array(r, by_group=by_group) if stack else r

    if isinstance(vec, DOFArray):
        actx = vec.array_context
        vec = conn(vec)

    from numbers import Number
    if by_group:
        if isinstance(vec, Number):
            return make_obj_array([
                np.full(grp.ndofs, vec) for grp in conn.to_discr.groups
                ])
        elif isinstance(vec, DOFArray):
            return make_obj_array([
                actx.to_numpy(ivec).reshape(-1) for ivec in vec
                ])
        else:
            raise TypeError(f"unsupported array type: {type(vec).__name__}")
    else:
        if isinstance(vec, Number):
            nnodes = sum(grp.ndofs for grp in conn.to_discr.groups)
            return np.full(nnodes, vec)
        elif isinstance(vec, DOFArray):
            return actx.to_numpy(flatten(vec))
        else:
            raise TypeError(f"unsupported array type: {type(vec).__name__}")
Beispiel #20
0
    def map_insn_rank_data_swap(self, insn, profile_data=None):
        local_data = self.array_context.to_numpy(flatten(self.rec(insn.field)))
        comm = self.discrwb.mpi_communicator

        # print("Sending data to rank %d with tag %d"
        #             % (insn.i_remote_rank, insn.send_tag))
        send_req = comm.Isend(local_data, insn.i_remote_rank, tag=insn.send_tag)

        remote_data_host = np.empty_like(local_data)
        recv_req = comm.Irecv(remote_data_host, insn.i_remote_rank, insn.recv_tag)

        return [], [
                MPIRecvFuture(
                    array_context=self.array_context,
                    bdry_discr=self.discrwb.discr_from_dd(insn.dd_out),
                    recv_req=recv_req,
                    insn_name=insn.name,
                    remote_data_host=remote_data_host),
                MPISendFuture(send_req)]
Beispiel #21
0
    def __init__(self, actx, discr, order, visualize=True, ylim=None):
        self.actx = actx
        self.dim = discr.ambient_dim

        self.visualize = visualize
        if not self.visualize:
            return

        if self.dim == 1:
            import matplotlib.pyplot as pt
            self.fig = pt.figure(figsize=(8, 8), dpi=300)
            self.ylim = ylim

            volume_discr = discr.discr_from_dd(dof_desc.DD_VOLUME)
            self.x = actx.to_numpy(flatten(thaw(actx,
                                                volume_discr.nodes()[0])))
        else:
            from grudge.shortcuts import make_visualizer
            self.vis = make_visualizer(discr)
Beispiel #22
0
    def __init__(self, actx, discr, order, visualize=True):
        self.actx = actx
        self.ambient_dim = discr.ambient_dim
        self.dim = discr.dim

        self.visualize = visualize
        if not self.visualize:
            return

        if self.ambient_dim == 2:
            import matplotlib.pyplot as pt
            self.fig = pt.figure(figsize=(8, 8), dpi=300)

            x = thaw(actx, discr.discr_from_dd(dof_desc.DD_VOLUME).nodes())
            self.x = actx.to_numpy(flatten(actx.np.atan2(x[1], x[0])))
        elif self.ambient_dim == 3:
            from grudge.shortcuts import make_visualizer
            self.vis = make_visualizer(discr)
        else:
            raise ValueError("unsupported dimension")
Beispiel #23
0
    def __init__(self, discrwb, remote_rank, vol_field, tag=None):
        self.tag = self.base_tag
        if tag is not None:
            self.tag += tag

        self.discrwb = discrwb
        self.array_context = vol_field.array_context
        self.remote_btag = BTAG_PARTITION(remote_rank)

        self.bdry_discr = discrwb.discr_from_dd(self.remote_btag)
        self.local_dof_array = discrwb.project("vol", self.remote_btag, vol_field)

        local_data = self.array_context.to_numpy(flatten(self.local_dof_array))

        comm = self.discrwb.mpi_communicator

        self.send_req = comm.Isend(
                local_data, remote_rank, tag=self.tag)

        self.remote_data_host = np.empty_like(local_data)
        self.recv_req = comm.Irecv(self.remote_data_host, remote_rank, self.tag)
Beispiel #24
0
    def my_checkpoint(step, t, dt, state):

        write_restart = (check_step(step, nrestart)
                         if step != restart_step else False)
        if write_restart is True:
            with open(snapshot_pattern.format(step=step, rank=rank), "wb") as f:
                pickle.dump({
                    "local_mesh": local_mesh,
                    "state": obj_array_vectorize(actx.to_numpy, flatten(state)),
                    "t": t,
                    "step": step,
                    "global_nelements": global_nelements,
                    "num_parts": nparts,
                    }, f)

        return sim_checkpoint(discr=discr, visualizer=visualizer, eos=eos,
                              q=state, vizname=casename,
                              step=step, t=t, dt=dt, nstatus=nstatus,
                              nviz=nviz, exittol=exittol,
                              constant_cfl=constant_cfl, comm=comm, vis_timer=vis_timer,
                              overwrite=True,s0=s0_sc,kappa=kappa_sc)
Beispiel #25
0
    def exec_compute_potential_insn_direct(self, actx, insn, bound_expr,
                                           evaluate, return_timing_data):
        from pytential import bind, sym
        if return_timing_data:
            from pytential.source import UnableToCollectTimingData
            from warnings import warn
            warn("Timing data collection not supported.",
                 category=UnableToCollectTimingData)

        lpot_applier = self.get_lpot_applier(insn.target_kernels,
                                             insn.source_kernels)
        p2p = None
        lpot_applier_on_tgt_subset = None

        from pytential.utils import flatten_if_needed
        kernel_args = {}
        for arg_name, arg_expr in insn.kernel_arguments.items():
            kernel_args[arg_name] = flatten_if_needed(actx, evaluate(arg_expr))

        waa = bind(
            bound_expr.places,
            sym.weights_and_area_elements(self.ambient_dim,
                                          dofdesc=insn.source))(actx)
        strength_vecs = [waa * evaluate(density) for density in insn.densities]

        from meshmode.discretization import Discretization
        flat_strengths = [flatten(strength) for strength in strength_vecs]

        source_discr = bound_expr.places.get_discretization(
            insn.source.geometry, insn.source.discr_stage)

        # FIXME: Do this all at once
        results = []
        for o in insn.outputs:
            source_dd = insn.source.copy(discr_stage=o.target_name.discr_stage)
            target_discr = bound_expr.places.get_discretization(
                o.target_name.geometry, o.target_name.discr_stage)
            density_discr = bound_expr.places.get_discretization(
                source_dd.geometry, source_dd.discr_stage)

            is_self = density_discr is target_discr
            if is_self:
                # QBXPreprocessor is supposed to have taken care of this
                assert o.qbx_forced_limit is not None
                assert abs(o.qbx_forced_limit) > 0

                expansion_radii = bind(
                    bound_expr.places,
                    sym.expansion_radii(self.ambient_dim,
                                        dofdesc=o.target_name))(actx)
                centers = bind(
                    bound_expr.places,
                    sym.expansion_centers(self.ambient_dim,
                                          o.qbx_forced_limit,
                                          dofdesc=o.target_name))(actx)

                evt, output_for_each_kernel = lpot_applier(
                    actx.queue,
                    flatten(thaw(actx, target_discr.nodes())),
                    flatten(thaw(actx, source_discr.nodes())),
                    flatten(centers),
                    flat_strengths,
                    expansion_radii=flatten(expansion_radii),
                    **kernel_args)

                result = output_for_each_kernel[o.target_kernel_index]
                if isinstance(target_discr, Discretization):
                    result = unflatten(actx, target_discr, result)

                results.append((o.name, result))
            else:
                # no on-disk kernel caching
                if p2p is None:
                    p2p = self.get_p2p(actx, insn.target_kernels,
                                       insn.source_kernels)
                if lpot_applier_on_tgt_subset is None:
                    lpot_applier_on_tgt_subset = self.get_lpot_applier_on_tgt_subset(
                        insn.target_kernels, insn.source_kernels)

                queue = actx.queue

                flat_targets = flatten_if_needed(actx, target_discr.nodes())
                flat_sources = flatten(thaw(actx, source_discr.nodes()))

                evt, output_for_each_kernel = p2p(queue, flat_targets,
                                                  flat_sources, flat_strengths,
                                                  **kernel_args)

                qbx_forced_limit = o.qbx_forced_limit
                if qbx_forced_limit is None:
                    qbx_forced_limit = 0

                target_discrs_and_qbx_sides = ((target_discr,
                                                qbx_forced_limit), )
                geo_data = self.qbx_fmm_geometry_data(
                    bound_expr.places,
                    insn.source.geometry,
                    target_discrs_and_qbx_sides=target_discrs_and_qbx_sides)

                # center-related info is independent of targets

                # First ncenters targets are the centers
                tgt_to_qbx_center = (
                    geo_data.user_target_to_center()[geo_data.ncenters:].copy(
                        queue=queue).with_queue(queue))

                qbx_tgt_numberer = self.get_qbx_target_numberer(
                    tgt_to_qbx_center.dtype)
                qbx_tgt_count = cl.array.empty(queue, (), np.int32)
                qbx_tgt_numbers = cl.array.empty_like(tgt_to_qbx_center)

                qbx_tgt_numberer(tgt_to_qbx_center,
                                 qbx_tgt_numbers,
                                 qbx_tgt_count,
                                 queue=queue)

                qbx_tgt_count = int(qbx_tgt_count.get())

                if (o.qbx_forced_limit is not None
                        and abs(o.qbx_forced_limit) == 1
                        and qbx_tgt_count < target_discr.ndofs):
                    raise RuntimeError("Did not find a matching QBX center "
                                       "for some targets")

                qbx_tgt_numbers = qbx_tgt_numbers[:qbx_tgt_count]
                qbx_center_numbers = tgt_to_qbx_center[qbx_tgt_numbers]
                qbx_center_numbers.finish()

                tgt_subset_kwargs = kernel_args.copy()
                for i, res_i in enumerate(output_for_each_kernel):
                    tgt_subset_kwargs[f"result_{i}"] = res_i

                if qbx_tgt_count:
                    lpot_applier_on_tgt_subset(
                        queue,
                        targets=flat_targets,
                        sources=flat_sources,
                        centers=geo_data.flat_centers(),
                        expansion_radii=geo_data.flat_expansion_radii(),
                        strengths=flat_strengths,
                        qbx_tgt_numbers=qbx_tgt_numbers,
                        qbx_center_numbers=qbx_center_numbers,
                        **tgt_subset_kwargs)

                result = output_for_each_kernel[o.target_kernel_index]
                if isinstance(target_discr, Discretization):
                    result = unflatten(actx, target_discr, result)

                results.append((o.name, result))

        timing_data = {}
        return results, timing_data
Beispiel #26
0
    def exec_compute_potential_insn_fmm(self, actx: PyOpenCLArrayContext, insn,
                                        bound_expr, evaluate, fmm_driver):
        """
        :arg fmm_driver: A function that accepts four arguments:
            *wrangler*, *strength*, *geo_data*, *kernel*, *kernel_arguments*
        :returns: a tuple ``(assignments, extra_outputs)``, where *assignments*
            is a list of tuples containing pairs ``(name, value)`` representing
            assignments to be performed in the evaluation context.
            *extra_outputs* is data that *fmm_driver* may return
            (such as timing data), passed through unmodified.
        """
        target_name_and_side_to_number, target_discrs_and_qbx_sides = (
            self.get_target_discrs_and_qbx_sides(insn, bound_expr))

        geo_data = self.qbx_fmm_geometry_data(bound_expr.places,
                                              insn.source.geometry,
                                              target_discrs_and_qbx_sides)

        # FIXME Exert more positive control over geo_data attribute lifetimes using
        # geo_data.<method>.clear_cache(geo_data).

        # FIXME Synthesize "bad centers" around corners and edges that have
        # inadequate QBX coverage.

        # FIXME don't compute *all* output kernels on all targets--respect that
        # some target discretizations may only be asking for derivatives (e.g.)

        from pytential import bind, sym
        waa = bind(
            bound_expr.places,
            sym.weights_and_area_elements(self.ambient_dim,
                                          dofdesc=insn.source))(actx)
        densities = [evaluate(density) for density in insn.densities]
        strengths = [waa * density for density in densities]
        flat_strengths = tuple(flatten(strength) for strength in strengths)

        base_kernel = single_valued(knl.get_base_kernel()
                                    for knl in insn.source_kernels)

        output_and_expansion_dtype = (self.get_fmm_output_and_expansion_dtype(
            insn.source_kernels, flat_strengths[0]))
        kernel_extra_kwargs, source_extra_kwargs = (
            self.get_fmm_expansion_wrangler_extra_kwargs(
                actx, insn.target_kernels + insn.source_kernels,
                geo_data.tree().user_source_ids, insn.kernel_arguments,
                evaluate))

        wrangler = self.expansion_wrangler_code_container(
            target_kernels=insn.target_kernels,
            source_kernels=insn.source_kernels).get_wrangler(
                actx.queue,
                geo_data,
                output_and_expansion_dtype,
                self.qbx_order,
                self.fmm_level_to_order,
                source_extra_kwargs=source_extra_kwargs,
                kernel_extra_kwargs=kernel_extra_kwargs,
                _use_target_specific_qbx=self._use_target_specific_qbx)

        from pytential.qbx.geometry import target_state
        if (actx.thaw(geo_data.user_target_to_center()) == target_state.FAILED
            ).any().get():
            raise RuntimeError("geometry has failed targets")

        # {{{ geometry data inspection hook

        if self.geometry_data_inspector is not None:
            perform_fmm = self.geometry_data_inspector(insn, bound_expr,
                                                       geo_data)
            if not perform_fmm:
                return [(o.name, 0) for o in insn.outputs]

        # }}}

        # Execute global QBX.
        all_potentials_on_every_target, extra_outputs = (fmm_driver(
            wrangler, flat_strengths, geo_data, base_kernel,
            kernel_extra_kwargs))

        results = []

        for o in insn.outputs:
            target_side_number = target_name_and_side_to_number[
                o.target_name, o.qbx_forced_limit]
            target_discr, _ = target_discrs_and_qbx_sides[target_side_number]
            target_slice = slice(*geo_data.target_info(
            ).target_discr_starts[target_side_number:target_side_number + 2])

            result = \
                all_potentials_on_every_target[o.target_kernel_index][target_slice]

            from meshmode.discretization import Discretization
            if isinstance(target_discr, Discretization):
                from meshmode.dof_array import unflatten
                result = unflatten(actx, target_discr, result)

            results.append((o.name, result))

        return results, extra_outputs
Beispiel #27
0
def _test_data_transfer(mpi_comm, actx, local_bdry_conns,
                        remote_to_local_bdry_conns, connected_parts):
    from mpi4py import MPI

    def f(x):
        return 10 * actx.np.sin(20. * x)

    """
    Here is a simplified example of what happens from
    the point of view of the local rank.

    Local rank:
        1. Transfer local points from local boundary to remote boundary
            to get remote points.
        2. Send remote points to remote rank.
    Remote rank:
        3. Receive remote points from local rank.
        4. Transfer remote points from remote boundary to local boundary
            to get local points.
        5. Send local points to local rank.
    Local rank:
        6. Receive local points from remote rank.
        7. Check if local points are the same as the original local points.
    """

    # 1.
    send_reqs = []
    for i_remote_part in connected_parts:
        conn = remote_to_local_bdry_conns[i_remote_part]
        bdry_discr = local_bdry_conns[i_remote_part].to_discr
        bdry_x = thaw(actx, bdry_discr.nodes()[0])

        true_local_f = f(bdry_x)
        remote_f = conn(true_local_f)

        # 2.
        send_reqs.append(
            mpi_comm.isend(actx.to_numpy(flatten(remote_f)),
                           dest=i_remote_part,
                           tag=TAG_SEND_REMOTE_NODES))

    # 3.
    buffers = {}
    for i_remote_part in connected_parts:
        status = MPI.Status()
        mpi_comm.probe(source=i_remote_part,
                       tag=TAG_SEND_REMOTE_NODES,
                       status=status)
        buffers[i_remote_part] = np.empty(status.count, dtype=bytes)

    recv_reqs = {}
    for i_remote_part, buf in buffers.items():
        recv_reqs[i_remote_part] = mpi_comm.irecv(buf=buf,
                                                  source=i_remote_part,
                                                  tag=TAG_SEND_REMOTE_NODES)
    remote_to_local_f_data = {}
    for i_remote_part, req in recv_reqs.items():
        remote_to_local_f_data[i_remote_part] = req.wait()
        buffers[i_remote_part] = None  # free buffer

    for req in send_reqs:
        req.wait()

    # 4.
    send_reqs = []
    for i_remote_part in connected_parts:
        conn = remote_to_local_bdry_conns[i_remote_part]
        local_f = unflatten(
            actx, conn.from_discr,
            actx.from_numpy(remote_to_local_f_data[i_remote_part]))
        remote_f = actx.to_numpy(flatten(conn(local_f)))

        # 5.
        send_reqs.append(
            mpi_comm.isend(remote_f,
                           dest=i_remote_part,
                           tag=TAG_SEND_LOCAL_NODES))

    # 6.
    buffers = {}
    for i_remote_part in connected_parts:
        status = MPI.Status()
        mpi_comm.probe(source=i_remote_part,
                       tag=TAG_SEND_LOCAL_NODES,
                       status=status)
        buffers[i_remote_part] = np.empty(status.count, dtype=bytes)

    recv_reqs = {}
    for i_remote_part, buf in buffers.items():
        recv_reqs[i_remote_part] = mpi_comm.irecv(buf=buf,
                                                  source=i_remote_part,
                                                  tag=TAG_SEND_LOCAL_NODES)
    local_f_data = {}
    for i_remote_part, req in recv_reqs.items():
        local_f_data[i_remote_part] = req.wait()
        buffers[i_remote_part] = None  # free buffer

    for req in send_reqs:
        req.wait()

    # 7.
    for i_remote_part in connected_parts:
        bdry_discr = local_bdry_conns[i_remote_part].to_discr
        bdry_x = thaw(actx, bdry_discr.nodes()[0])

        true_local_f = actx.to_numpy(flatten(f(bdry_x)))
        local_f = local_f_data[i_remote_part]

        from numpy.linalg import norm
        err = norm(true_local_f - local_f, np.inf)
        assert err < 1e-11, "Error = %f is too large" % err
Beispiel #28
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)
Beispiel #29
0
def test_mass_mat_trig(ctx_factory, ambient_dim, quad_tag):
    """Check the integral of some trig functions on an interval using the mass
    matrix.
    """
    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue)

    nelements = 17
    order = 4

    a = -4.0 * np.pi
    b = +9.0 * np.pi
    true_integral = 13 * np.pi / 2 * (b - a)**(ambient_dim - 1)

    from meshmode.discretization.poly_element import QuadratureSimplexGroupFactory
    dd_quad = sym.DOFDesc(sym.DTAG_VOLUME_ALL, quad_tag)
    if quad_tag is sym.QTAG_NONE:
        quad_tag_to_group_factory = {}
    else:
        quad_tag_to_group_factory = {
            quad_tag: QuadratureSimplexGroupFactory(order=2 * order)
        }

    from meshmode.mesh.generation import generate_regular_rect_mesh
    mesh = generate_regular_rect_mesh(a=(a, ) * ambient_dim,
                                      b=(b, ) * ambient_dim,
                                      n=(nelements, ) * ambient_dim,
                                      order=1)
    discr = DGDiscretizationWithBoundaries(
        actx,
        mesh,
        order=order,
        quad_tag_to_group_factory=quad_tag_to_group_factory)

    def _get_variables_on(dd):
        sym_f = sym.var("f", dd=dd)
        sym_x = sym.nodes(ambient_dim, dd=dd)
        sym_ones = sym.Ones(dd)

        return sym_f, sym_x, sym_ones

    sym_f, sym_x, sym_ones = _get_variables_on(sym.DD_VOLUME)
    f_volm = actx.to_numpy(flatten(bind(discr, sym.cos(sym_x[0])**2)(actx)))
    ones_volm = actx.to_numpy(flatten(bind(discr, sym_ones)(actx)))

    sym_f, sym_x, sym_ones = _get_variables_on(dd_quad)
    f_quad = bind(discr, sym.cos(sym_x[0])**2)(actx)
    ones_quad = bind(discr, sym_ones)(actx)

    mass_op = bind(discr, sym.MassOperator(dd_quad, sym.DD_VOLUME)(sym_f))

    num_integral_1 = np.dot(ones_volm,
                            actx.to_numpy(flatten(mass_op(f=f_quad))))
    err_1 = abs(num_integral_1 - true_integral)
    assert err_1 < 1e-9, err_1

    num_integral_2 = np.dot(f_volm,
                            actx.to_numpy(flatten(mass_op(f=ones_quad))))
    err_2 = abs(num_integral_2 - true_integral)
    assert err_2 < 1.0e-9, err_2

    if quad_tag is sym.QTAG_NONE:
        # NOTE: `integral` always makes a square mass matrix and
        # `QuadratureSimplexGroupFactory` does not have a `mass_matrix` method.
        num_integral_3 = bind(discr, sym.integral(sym_f, dd=dd_quad))(f=f_quad)
        err_3 = abs(num_integral_3 - true_integral)
        assert err_3 < 5.0e-10, err_3
def test_ellipse_eigenvalues(ctx_factory,
                             ellipse_aspect,
                             mode_nr,
                             qbx_order,
                             force_direct,
                             visualize=False):
    logging.basicConfig(level=logging.INFO)

    print("ellipse_aspect: %s, mode_nr: %d, qbx_order: %d" %
          (ellipse_aspect, mode_nr, qbx_order))

    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue)

    target_order = 8

    from meshmode.discretization import Discretization
    from meshmode.discretization.poly_element import \
            InterpolatoryQuadratureSimplexGroupFactory
    from pytential.qbx import QBXLayerPotentialSource
    from pytools.convergence import EOCRecorder

    s_eoc_rec = EOCRecorder()
    d_eoc_rec = EOCRecorder()
    sp_eoc_rec = EOCRecorder()

    if ellipse_aspect != 1:
        nelements_values = [60, 100, 150, 200]
    else:
        nelements_values = [30, 70]

    # See
    #
    # [1] G. J. Rodin and O. Steinbach, "Boundary Element Preconditioners
    # for Problems Defined on Slender Domains", SIAM Journal on Scientific
    # Computing, Vol. 24, No. 4, pg. 1450, 2003.
    # https://dx.doi.org/10.1137/S1064827500372067

    for nelements in nelements_values:
        mesh = make_curve_mesh(partial(ellipse, ellipse_aspect),
                               np.linspace(0, 1, nelements + 1), target_order)

        fmm_order = 12
        if force_direct:
            fmm_order = False

        pre_density_discr = Discretization(
            actx, mesh,
            InterpolatoryQuadratureSimplexGroupFactory(target_order))
        qbx = QBXLayerPotentialSource(
            pre_density_discr,
            4 * target_order,
            qbx_order,
            fmm_order=fmm_order,
            _expansions_in_tree_have_extent=True,
        )
        places = GeometryCollection(qbx)

        density_discr = places.get_discretization(places.auto_source.geometry)
        from meshmode.dof_array import thaw, flatten
        nodes = thaw(actx, density_discr.nodes())

        if visualize:
            # plot geometry, centers, normals
            centers = bind(places, sym.expansion_centers(qbx.ambient_dim,
                                                         +1))(actx)
            normals = bind(places,
                           sym.normal(qbx.ambient_dim))(actx).as_vector(object)

            nodes_h = np.array(
                [actx.to_numpy(axis) for axis in flatten(nodes)])
            centers_h = np.array(
                [actx.to_numpy(axis) for axis in flatten(centers)])
            normals_h = np.array(
                [actx.to_numpy(axis) for axis in flatten(normals)])

            pt.plot(nodes_h[0], nodes_h[1], "x-")
            pt.plot(centers_h[0], centers_h[1], "o")
            pt.quiver(nodes_h[0], nodes_h[1], normals_h[0], normals_h[1])
            pt.gca().set_aspect("equal")
            pt.show()

        angle = actx.np.arctan2(nodes[1] * ellipse_aspect, nodes[0])

        ellipse_fraction = ((1 - ellipse_aspect) /
                            (1 + ellipse_aspect))**mode_nr

        # (2.6) in [1]
        J = actx.np.sqrt(  # noqa
            actx.np.sin(angle)**2 +
            (1 / ellipse_aspect)**2 * actx.np.cos(angle)**2)

        from sumpy.kernel import LaplaceKernel
        lap_knl = LaplaceKernel(2)

        # {{{ single layer

        sigma_sym = sym.var("sigma")
        s_sigma_op = sym.S(lap_knl, sigma_sym, qbx_forced_limit=+1)

        sigma = actx.np.cos(mode_nr * angle) / J
        s_sigma = bind(places, s_sigma_op)(actx, sigma=sigma)

        # SIGN BINGO! :)
        s_eigval = 1 / (2 * mode_nr) * (1 + (-1)**mode_nr * ellipse_fraction)

        # (2.12) in [1]
        s_sigma_ref = s_eigval * J * sigma

        if 0:
            #pt.plot(s_sigma.get(), label="result")
            #pt.plot(s_sigma_ref.get(), label="ref")
            pt.plot(actx.to_numpy(flatten(s_sigma_ref - s_sigma)), label="err")
            pt.legend()
            pt.show()

        h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx)
        s_err = (norm(density_discr, s_sigma - s_sigma_ref) /
                 norm(density_discr, s_sigma_ref))
        s_eoc_rec.add_data_point(h_max, s_err)

        # }}}

        # {{{ double layer

        d_sigma_op = sym.D(lap_knl, sigma_sym, qbx_forced_limit="avg")

        sigma = actx.np.cos(mode_nr * angle)
        d_sigma = bind(places, d_sigma_op)(actx, sigma=sigma)

        # SIGN BINGO! :)
        d_eigval = -(-1)**mode_nr * 1 / 2 * ellipse_fraction

        d_sigma_ref = d_eigval * sigma

        if 0:
            pt.plot(actx.to_numpy(flatten(d_sigma)), label="result")
            pt.plot(actx.to_numpy(flatten(d_sigma_ref)), label="ref")
            pt.legend()
            pt.show()

        if ellipse_aspect == 1:
            d_ref_norm = norm(density_discr, sigma)
        else:
            d_ref_norm = norm(density_discr, d_sigma_ref)

        d_err = (norm(density_discr, d_sigma - d_sigma_ref) / d_ref_norm)
        d_eoc_rec.add_data_point(h_max, d_err)

        # }}}

        if ellipse_aspect == 1:
            # {{{ S'

            sp_sigma_op = sym.Sp(lap_knl,
                                 sym.var("sigma"),
                                 qbx_forced_limit="avg")

            sigma = actx.np.cos(mode_nr * angle)
            sp_sigma = bind(places, sp_sigma_op)(actx, sigma=sigma)
            sp_eigval = 0

            sp_sigma_ref = sp_eigval * sigma

            sp_err = (norm(density_discr, sp_sigma - sp_sigma_ref) /
                      norm(density_discr, sigma))
            sp_eoc_rec.add_data_point(h_max, sp_err)

            # }}}

    print("Errors for S:")
    print(s_eoc_rec)
    required_order = qbx_order + 1
    assert s_eoc_rec.order_estimate() > required_order - 1.5

    print("Errors for D:")
    print(d_eoc_rec)
    required_order = qbx_order
    assert d_eoc_rec.order_estimate() > required_order - 1.5

    if ellipse_aspect == 1:
        print("Errors for S':")
        print(sp_eoc_rec)
        required_order = qbx_order
        assert sp_eoc_rec.order_estimate() > required_order - 1.5