Exemple #1
0
    def __init__(
        self,
        density_discr,
        fmm_order=False,
        fmm_level_to_order=None,
        expansion_factory=None,
        # begin undocumented arguments
        # FIXME default debug=False once everything works
        debug=True):
        """
        :arg fmm_order: `False` for direct calculation.
        """
        LayerPotentialSourceBase.__init__(self, density_discr)
        self.debug = debug

        if fmm_order is not False and fmm_level_to_order is not None:
            raise TypeError(
                "may not specify both fmm_order and fmm_level_to_order")

        if fmm_level_to_order is None:
            if fmm_order is not False:

                def fmm_level_to_order(kernel, kernel_args, tree, level):  # noqa pylint:disable=function-redefined
                    return fmm_order
            else:
                fmm_level_to_order = False

        self.density_discr = density_discr
        self.fmm_level_to_order = fmm_level_to_order

        if expansion_factory is None:
            from sumpy.expansion import DefaultExpansionFactory
            expansion_factory = DefaultExpansionFactory()
        self.expansion_factory = expansion_factory
Exemple #2
0
    def __init__(self,
                 cl_context,
                 kernel,
                 mpole_expn_class=None,
                 local_expn_class=None,
                 expansion_factory=None,
                 extra_source_kwargs=None,
                 extra_kernel_kwargs=None):
        self.cl_context = cl_context
        self.queue = cl.CommandQueue(self.cl_context)
        self.kernel = kernel

        self.no_target_deriv_kernel = TargetDerivativeRemover()(kernel)

        if expansion_factory is None:
            from sumpy.expansion import DefaultExpansionFactory
            expansion_factory = DefaultExpansionFactory()
        if mpole_expn_class is None:
            mpole_expn_class = \
                    expansion_factory.get_multipole_expansion_class(kernel)
        if local_expn_class is None:
            local_expn_class = \
                    expansion_factory.get_local_expansion_class(kernel)

        self.mpole_expn_class = mpole_expn_class
        self.local_expn_class = local_expn_class

        if extra_source_kwargs is None:
            extra_source_kwargs = {}
        if extra_kernel_kwargs is None:
            extra_kernel_kwargs = {}

        self.extra_source_kwargs = extra_source_kwargs
        self.extra_kernel_kwargs = extra_kernel_kwargs

        extra_source_and_kernel_kwargs = extra_source_kwargs.copy()
        extra_source_and_kernel_kwargs.update(extra_kernel_kwargs)
        self.extra_source_and_kernel_kwargs = extra_source_and_kernel_kwargs
Exemple #3
0
    def __init__(self, cl_context, kernel,
            mpole_expn_class=None,
            local_expn_class=None,
            expansion_factory=None,
            extra_source_kwargs=None,
            extra_kernel_kwargs=None):
        self.cl_context = cl_context
        self.queue = cl.CommandQueue(self.cl_context)
        self.kernel = kernel

        self.no_target_deriv_kernel = TargetDerivativeRemover()(kernel)

        if expansion_factory is None:
            from sumpy.expansion import DefaultExpansionFactory
            expansion_factory = DefaultExpansionFactory()
        if mpole_expn_class is None:
            mpole_expn_class = \
                    expansion_factory.get_multipole_expansion_class(kernel)
        if local_expn_class is None:
            local_expn_class = \
                    expansion_factory.get_local_expansion_class(kernel)

        self.mpole_expn_class = mpole_expn_class
        self.local_expn_class = local_expn_class

        if extra_source_kwargs is None:
            extra_source_kwargs = {}
        if extra_kernel_kwargs is None:
            extra_kernel_kwargs = {}

        self.extra_source_kwargs = extra_source_kwargs
        self.extra_kernel_kwargs = extra_kernel_kwargs

        extra_source_and_kernel_kwargs = extra_source_kwargs.copy()
        extra_source_and_kernel_kwargs.update(extra_kernel_kwargs)
        self.extra_source_and_kernel_kwargs = extra_source_and_kernel_kwargs
Exemple #4
0
    def __init__(
            self,
            density_discr,
            fine_order,
            qbx_order=None,
            fmm_order=None,
            fmm_level_to_order=None,
            expansion_factory=None,
            target_association_tolerance=_not_provided,

            # begin experimental arguments
            # FIXME default debug=False once everything has matured
            debug=True,
            _disable_refinement=False,
            _expansions_in_tree_have_extent=True,
            _expansion_stick_out_factor=0.5,
            _well_sep_is_n_away=2,
            _max_leaf_refine_weight=None,
            _box_extent_norm=None,
            _from_sep_smaller_crit=None,
            _from_sep_smaller_min_nsources_cumul=None,
            _tree_kind="adaptive",
            _use_target_specific_qbx=None,
            geometry_data_inspector=None,
            cost_model=None,
            fmm_backend="sumpy",
            target_stick_out_factor=_not_provided):
        """
        :arg fine_order: The total degree to which the (upsampled)
             underlying quadrature is exact.
        :arg fmm_order: `False` for direct calculation. May not be given if
            *fmm_level_to_order* is given.
        :arg fmm_level_to_order: A function that takes arguments of
             *(kernel, kernel_args, tree, level)* and returns the expansion
             order to be used on a given *level* of *tree* with *kernel*, where
             *kernel* is the :class:`sumpy.kernel.Kernel` being evaluated, and
             *kernel_args* is a set of *(key, value)* tuples with evaluated
             kernel arguments. May not be given if *fmm_order* is given.

        Experimental arguments without a promise of forward compatibility:

        :arg _use_target_specific_qbx: Whether to use target-specific
            acceleration by default if possible. *None* means
            "use if possible".
        :arg cost_model: Either *None* or an object implementing the
             :class:`~pytential.qbx.cost.AbstractQBXCostModel` interface, used for
             gathering modeled costs if provided (experimental)
        """

        # {{{ argument processing

        if fine_order is None:
            raise ValueError("fine_order must be provided.")

        if qbx_order is None:
            raise ValueError("qbx_order must be provided.")

        if target_stick_out_factor is not _not_provided:
            from warnings import warn
            warn(
                "target_stick_out_factor has been renamed to "
                "target_association_tolerance. "
                "Using target_stick_out_factor is deprecated "
                "and will stop working in 2018.",
                DeprecationWarning,
                stacklevel=2)

            if target_association_tolerance is not _not_provided:
                raise TypeError(
                    "May not pass both target_association_tolerance and "
                    "target_stick_out_factor.")

            target_association_tolerance = target_stick_out_factor

        del target_stick_out_factor

        if target_association_tolerance is _not_provided:
            target_association_tolerance = float(
                np.finfo(density_discr.real_dtype).eps) * 1e3

        if fmm_order is not None and fmm_level_to_order is not None:
            raise TypeError(
                "may not specify both fmm_order and fmm_level_to_order")

        if _box_extent_norm is None:
            _box_extent_norm = "l2"

        if _from_sep_smaller_crit is None:
            # This seems to win no matter what the box extent norm is
            # https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/10
            _from_sep_smaller_crit = "precise_linf"

        if fmm_level_to_order is None:
            if fmm_order is False:
                fmm_level_to_order = False
            else:

                def fmm_level_to_order(kernel, kernel_args, tree, level):  # noqa pylint:disable=function-redefined
                    return fmm_order

        if _max_leaf_refine_weight is None:
            if density_discr.ambient_dim == 2:
                # FIXME: This should be verified now that l^2 is the default.
                _max_leaf_refine_weight = 64
            elif density_discr.ambient_dim == 3:
                # For static_linf/linf: https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/8#note_25009  # noqa
                # For static_l2/l2: https://gitlab.tiker.net/papers/2017-qbx-fmm-3d/issues/12  # noqa
                _max_leaf_refine_weight = 512
            else:
                # Just guessing...
                _max_leaf_refine_weight = 64

        if _from_sep_smaller_min_nsources_cumul is None:
            # See here for the comment thread that led to these defaults:
            # https://gitlab.tiker.net/inducer/boxtree/merge_requests/28#note_18661
            if density_discr.dim == 1:
                _from_sep_smaller_min_nsources_cumul = 15
            else:
                _from_sep_smaller_min_nsources_cumul = 30

        # }}}

        LayerPotentialSourceBase.__init__(self, density_discr)

        self.fine_order = fine_order
        self.qbx_order = qbx_order
        self.fmm_level_to_order = fmm_level_to_order

        assert target_association_tolerance is not None

        self.target_association_tolerance = target_association_tolerance
        self.fmm_backend = fmm_backend

        if expansion_factory is None:
            from sumpy.expansion import DefaultExpansionFactory
            expansion_factory = DefaultExpansionFactory()
        self.expansion_factory = expansion_factory

        self.debug = debug
        self._disable_refinement = _disable_refinement
        self._expansions_in_tree_have_extent = \
                _expansions_in_tree_have_extent
        self._expansion_stick_out_factor = _expansion_stick_out_factor
        self._well_sep_is_n_away = _well_sep_is_n_away
        self._max_leaf_refine_weight = _max_leaf_refine_weight
        self._box_extent_norm = _box_extent_norm
        self._from_sep_smaller_crit = _from_sep_smaller_crit
        self._from_sep_smaller_min_nsources_cumul = \
                _from_sep_smaller_min_nsources_cumul
        self._tree_kind = _tree_kind
        self._use_target_specific_qbx = _use_target_specific_qbx
        self.geometry_data_inspector = geometry_data_inspector

        if cost_model is None:
            from pytential.qbx.cost import QBXCostModel
            cost_model = QBXCostModel()

        self.cost_model = cost_model
Exemple #5
0
def main():

    print("*************************")
    print("* Setting up...")
    print("*************************")

    dim = 2

    # download precomputation results for the 2D Laplace kernel
    download_table = True
    table_filename = "nft_laplace2d.hdf5"
    root_table_source_extent = 2

    print("Using table cache:", table_filename)

    q_order = 9  # quadrature order
    n_levels = 6  # 2^(n_levels-1) subintervals in 1D

    use_multilevel_table = False

    adaptive_mesh = False
    n_refinement_loops = 100
    refined_n_cells = 2000
    rratio_top = 0.2
    rratio_bot = 0.5

    dtype = np.float64

    m_order = 20  # multipole order
    force_direct_evaluation = False

    print("Multipole order =", m_order)

    alpha = 160

    x = pmbl.var("x")
    y = pmbl.var("y")
    expp = pmbl.var("exp")

    norm2 = x**2 + y**2
    source_expr = -(4 * alpha**2 * norm2 - 4 * alpha) * expp(-alpha * norm2)
    solu_expr = expp(-alpha * norm2)

    logger.info("Source expr: " + str(source_expr))
    logger.info("Solu expr: " + str(solu_expr))

    # bounding box
    a = -0.5
    b = 0.5
    root_table_source_extent = 2

    ctx = cl.create_some_context()
    queue = cl.CommandQueue(ctx)

    source_eval = Eval(dim, source_expr, [x, y])

    # {{{ generate quad points

    import volumential.meshgen as mg

    # Show meshgen info
    mg.greet()

    mesh = mg.MeshGen2D(q_order, n_levels, a, b, queue=queue)
    if not adaptive_mesh:
        mesh.print_info()
        q_points = mesh.get_q_points()
        q_weights = mesh.get_q_weights()

    else:
        iloop = -1
        while mesh.n_active_cells() < refined_n_cells:
            iloop += 1
            crtr = np.abs(
                source_eval(mesh.get_cell_centers) * mesh.get_cell_measures)
            mesh.update_mesh(crtr, rratio_top, rratio_bot)
            if iloop > n_refinement_loops:
                print("Max number of refinement loops reached.")
                break

        mesh.print_info()
        q_points = mesh.get_q_points()
        q_weights = mesh.get_q_weights()

    assert len(q_points) == len(q_weights)
    assert q_points.shape[1] == dim

    q_points = np.ascontiguousarray(np.transpose(q_points))

    from pytools.obj_array import make_obj_array

    q_points = make_obj_array(
        [cl.array.to_device(queue, q_points[i]) for i in range(dim)])

    q_weights = cl.array.to_device(queue, q_weights)
    # q_radii = cl.array.to_device(queue, q_radii)

    # }}}

    # {{{ discretize the source field

    source_vals = cl.array.to_device(
        queue,
        source_eval(queue, np.array([coords.get() for coords in q_points])))

    # particle_weigt = source_val * q_weight

    # }}} End discretize the source field

    # {{{ build tree and traversals

    from boxtree.tools import AXIS_NAMES

    axis_names = AXIS_NAMES[:dim]

    from pytools import single_valued

    coord_dtype = single_valued(coord.dtype for coord in q_points)
    from boxtree.bounding_box import make_bounding_box_dtype

    bbox_type, _ = make_bounding_box_dtype(ctx.devices[0], dim, coord_dtype)
    bbox = np.empty(1, bbox_type)
    for ax in axis_names:
        bbox["min_" + ax] = a
        bbox["max_" + ax] = b

    # tune max_particles_in_box to reconstruct the mesh
    # TODO: use points from FieldPlotter are used as target points for better
    # visuals
    from boxtree import TreeBuilder

    tb = TreeBuilder(ctx)
    tree, _ = tb(
        queue,
        particles=q_points,
        targets=q_points,
        bbox=bbox,
        max_particles_in_box=q_order**2 * 4 - 1,
        kind="adaptive-level-restricted",
    )

    bbox2 = np.array([[a, b], [a, b]])
    tree2, _ = tb(
        queue,
        particles=q_points,
        targets=q_points,
        bbox=bbox2,
        max_particles_in_box=q_order**2 * 4 - 1,
        kind="adaptive-level-restricted",
    )

    from boxtree.traversal import FMMTraversalBuilder

    tg = FMMTraversalBuilder(ctx)
    trav, _ = tg(queue, tree)

    # }}} End build tree and traversals

    # {{{ build near field potential table

    from volumential.table_manager import NearFieldInteractionTableManager
    import os

    if download_table and (not os.path.isfile(table_filename)):
        import json
        with open("table_urls.json", 'r') as fp:
            urls = json.load(fp)

        print("Downloading table from %s" % urls['Laplace2D'])
        import subprocess
        subprocess.call(["wget", "-q", urls['Laplace2D'], table_filename])

    tm = NearFieldInteractionTableManager(table_filename,
                                          root_extent=root_table_source_extent,
                                          queue=queue)

    if use_multilevel_table:
        assert (abs(
            int((b - a) / root_table_source_extent) *
            root_table_source_extent - (b - a)) < 1e-15)
        nftable = []
        for lev in range(0, tree.nlevels + 1):
            print("Getting table at level", lev)
            tb, _ = tm.get_table(
                dim,
                "Laplace",
                q_order,
                source_box_level=lev,
                compute_method="DrosteSum",
                queue=queue,
                n_brick_quad_points=100,
                adaptive_level=False,
                use_symmetry=True,
                alpha=0.1,
                nlevels=15,
            )
            nftable.append(tb)

        print("Using table list of length", len(nftable))

    else:
        nftable, _ = tm.get_table(
            dim,
            "Laplace",
            q_order,
            force_recompute=False,
            compute_method="DrosteSum",
            queue=queue,
            n_brick_quad_points=100,
            adaptive_level=False,
            use_symmetry=True,
            alpha=0.1,
            nlevels=15,
        )

    # }}} End build near field potential table

    # {{{ sumpy expansion for laplace kernel

    from sumpy.expansion import DefaultExpansionFactory
    from sumpy.kernel import LaplaceKernel

    knl = LaplaceKernel(dim)
    out_kernels = [knl]

    expn_factory = DefaultExpansionFactory()
    local_expn_class = expn_factory.get_local_expansion_class(knl)
    mpole_expn_class = expn_factory.get_multipole_expansion_class(knl)

    exclude_self = True

    from volumential.expansion_wrangler_fpnd import (
        FPNDExpansionWranglerCodeContainer, FPNDExpansionWrangler)

    wcc = FPNDExpansionWranglerCodeContainer(
        ctx,
        partial(mpole_expn_class, knl),
        partial(local_expn_class, knl),
        out_kernels,
        exclude_self=exclude_self,
    )

    if exclude_self:
        target_to_source = np.arange(tree.ntargets, dtype=np.int32)
        self_extra_kwargs = {"target_to_source": target_to_source}
    else:
        self_extra_kwargs = {}

    wrangler = FPNDExpansionWrangler(
        code_container=wcc,
        queue=queue,
        tree=tree,
        near_field_table=nftable,
        dtype=dtype,
        fmm_level_to_order=lambda kernel, kernel_args, tree, lev: m_order,
        quad_order=q_order,
        self_extra_kwargs=self_extra_kwargs,
    )

    # }}} End sumpy expansion for laplace kernel

    print("*************************")
    print("* Performing FMM ...")
    print("*************************")

    # {{{ conduct fmm computation

    from volumential.volume_fmm import drive_volume_fmm

    import time
    queue.finish()

    t0 = time.time()

    pot, = drive_volume_fmm(
        trav,
        wrangler,
        source_vals * q_weights,
        source_vals,
        direct_evaluation=force_direct_evaluation,
    )
    queue.finish()

    t1 = time.time()

    print("Finished in %.2f seconds." % (t1 - t0))
    print("(%e points per second)" % (len(q_weights) / (t1 - t0)))

    # }}} End conduct fmm computation

    print("*************************")
    print("* Postprocessing ...")
    print("*************************")

    # {{{ postprocess and plot

    # print(pot)

    solu_eval = Eval(dim, solu_expr, [x, y])

    x = q_points[0].get()
    y = q_points[1].get()
    ze = solu_eval(queue, np.array([x, y]))
    zs = pot.get()

    print_error = True
    if print_error:
        err = np.max(np.abs(ze - zs))
        print("Error =", err)

    # Interpolated surface
    if 0:
        h = 0.005
        out_x = np.arange(a, b + h, h)
        out_y = np.arange(a, b + h, h)
        oxx, oyy = np.meshgrid(out_x, out_y)
        out_targets = make_obj_array([
            cl.array.to_device(queue, oxx.flatten()),
            cl.array.to_device(queue, oyy.flatten()),
        ])

        from volumential.volume_fmm import interpolate_volume_potential

        # src = source_field([q.get() for q in q_points])
        # src = cl.array.to_device(queue, src)
        interp_pot = interpolate_volume_potential(out_targets, trav, wrangler,
                                                  pot)
        opot = interp_pot.get()

        import matplotlib.pyplot as plt
        from mpl_toolkits.mplot3d import Axes3D

        plt3d = plt.figure()
        ax = Axes3D(plt3d)  # noqa
        surf = ax.plot_surface(oxx, oyy, opot.reshape(oxx.shape))  # noqa
        # ax.scatter(x, y, src.get())
        # ax.set_zlim(-0.25, 0.25)

        plt.draw()
        plt.show()

    # Boxtree
    if 0:
        import matplotlib.pyplot as plt

        if dim == 2:
            # plt.plot(q_points[0].get(), q_points[1].get(), ".")
            pass

        from boxtree.visualization import TreePlotter

        plotter = TreePlotter(tree.get(queue=queue))
        plotter.draw_tree(fill=False, edgecolor="black")
        # plotter.draw_box_numbers()
        plotter.set_bounding_box()
        plt.gca().set_aspect("equal")

        plt.draw()
        # plt.show()
        plt.savefig("tree.png")

    # Direct p2p
    if 0:
        print("Performing P2P")
        pot_direct, = drive_volume_fmm(trav,
                                       wrangler,
                                       source_vals * q_weights,
                                       source_vals,
                                       direct_evaluation=True)
        zds = pot_direct.get()
        zs = pot.get()

        print("P2P-FMM diff =", np.max(np.abs(zs - zds)))

        print("P2P Error =", np.max(np.abs(ze - zds)))
        """
        import matplotlib.pyplot as plt
        import matplotlib.cm as cm
        x = q_points[0].get()
        y = q_points[1].get()
        plt.scatter(x, y, c=np.log(abs(zs-zds)) / np.log(10), cmap=cm.jet)
        plt.colorbar()

        plt.xlabel("Multipole order = " + str(m_order))

        plt.draw()
        plt.show()
        """

    # Scatter plot
    if 0:
        import matplotlib.pyplot as plt
        from mpl_toolkits.mplot3d import Axes3D

        x = q_points[0].get()
        y = q_points[1].get()
        ze = solu_eval(queue, np.array([x, y]))
        zs = pot.get()

        plt3d = plt.figure()
        ax = Axes3D(plt3d)
        ax.scatter(x, y, zs, s=1)
        # ax.scatter(x, y, source_field([q.get() for q in q_points]), s=1)
        # import matplotlib.cm as cm

        # ax.scatter(x, y, zs, c=np.log(abs(zs-zds)), cmap=cm.jet)
        # plt.gca().set_aspect("equal")

        # ax.set_xlim3d([-1, 1])
        # ax.set_ylim3d([-1, 1])
        # ax.set_zlim3d([np.min(z), np.max(z)])
        # ax.set_zlim3d([-0.002, 0.00])

        plt.draw()
        plt.show()
Exemple #6
0
        tb_dx.integral_knl.__repr__(): tb_dx,
        tb_dy.integral_knl.__repr__(): tb_dy,
    }

# }}} End build near field potential table

# {{{ sumpy expansion for laplace kernel

from sumpy.expansion import DefaultExpansionFactory
from sumpy.kernel import LaplaceKernel, AxisTargetDerivative

knl = LaplaceKernel(dim)
knl_dx = AxisTargetDerivative(0, knl)
knl_dy = AxisTargetDerivative(1, knl)

expn_factory = DefaultExpansionFactory()
local_expn_class = expn_factory.get_local_expansion_class(knl)
mpole_expn_class = expn_factory.get_multipole_expansion_class(knl)
out_kernels = [knl, knl_dx, knl_dy]

exclude_self = True
from volumential.expansion_wrangler_fpnd import (
    FPNDExpansionWranglerCodeContainer, FPNDExpansionWrangler)

wcc = FPNDExpansionWranglerCodeContainer(
    ctx,
    partial(mpole_expn_class, knl),
    partial(local_expn_class, knl),
    out_kernels,
    exclude_self=exclude_self,
)
Exemple #7
0
def main():

    print("*************************")
    print("* Setting up...")
    print("*************************")

    dim = 3
    # download precomputation results for the 3D Laplace kernel
    download_table = True
    table_filename = "nft_laplace3d.hdf5"

    logger.info("Using table cache: " + table_filename)

    q_order = 7  # quadrature order
    n_levels = 5
    use_multilevel_table = False

    adaptive_mesh = False
    n_refinement_loops = 100
    refined_n_cells = 5e5
    rratio_top = 0.2
    rratio_bot = 0.5

    dtype = np.float64

    m_order = 10  # multipole order
    force_direct_evaluation = False

    logger.info("Multipole order = " + str(m_order))
    logger.info("Quad order = " + str(q_order))
    logger.info("N_levels = " + str(n_levels))

    # a solution that is nearly zero at the boundary
    # exp(-40) = 4.25e-18
    alpha = 80
    x = pmbl.var("x")
    y = pmbl.var("y")
    z = pmbl.var("z")
    expp = pmbl.var("exp")

    norm2 = x**2 + y**2 + z**2
    source_expr = -(4 * alpha**2 * norm2 - 6 * alpha) * expp(-alpha * norm2)
    solu_expr = expp(-alpha * norm2)

    logger.info("Source expr: " + str(source_expr))
    logger.info("Solu expr: " + str(solu_expr))

    # bounding box
    a = -0.5
    b = 0.5
    root_table_source_extent = 2

    ctx = cl.create_some_context()
    queue = cl.CommandQueue(ctx)

    # logger.info("Summary of params: " + get_param_summary())
    source_eval = Eval(dim, source_expr, [x, y, z])

    # {{{ generate quad points

    import volumential.meshgen as mg

    # Show meshgen info
    mg.greet()

    mesh = mg.MeshGen3D(q_order, n_levels, a, b, queue=queue)
    if not adaptive_mesh:
        mesh.print_info()
        q_points = mesh.get_q_points()
        q_weights = mesh.get_q_weights()
    else:
        iloop = -1
        while mesh.n_active_cells() < refined_n_cells:
            iloop += 1
            cell_centers = mesh.get_cell_centers()
            cell_measures = mesh.get_cell_measures()
            density_vals = source_eval(
                queue,
                np.array([[center[d] for center in cell_centers]
                          for d in range(dim)]))
            crtr = np.abs(cell_measures * density_vals)
            mesh.update_mesh(crtr, rratio_top, rratio_bot)
            if iloop > n_refinement_loops:
                print("Max number of refinement loops reached.")
                break

        mesh.print_info()
        q_points = mesh.get_q_points()
        q_weights = mesh.get_q_weights()

    if 1:
        try:
            mesh.generate_gmsh("box_grid.msh")
        except Exception as e:
            print(e)
            pass

        legacy_msh_file = True
        if legacy_msh_file:
            import os

            os.system("gmsh box_grid.msh convert_grid -")

    assert len(q_points) == len(q_weights)
    assert q_points.shape[1] == dim

    q_points = np.ascontiguousarray(np.transpose(q_points))

    from pytools.obj_array import make_obj_array

    q_points = make_obj_array(
        [cl.array.to_device(queue, q_points[i]) for i in range(dim)])

    q_weights = cl.array.to_device(queue, q_weights)

    # }}}

    # {{{ discretize the source field

    logger.info("discretizing source field")
    source_vals = cl.array.to_device(
        queue,
        source_eval(queue, np.array([coords.get() for coords in q_points])))

    # particle_weigt = source_val * q_weight

    # }}} End discretize the source field

    # {{{ build tree and traversals

    from boxtree.tools import AXIS_NAMES

    axis_names = AXIS_NAMES[:dim]

    from pytools import single_valued

    coord_dtype = single_valued(coord.dtype for coord in q_points)
    from boxtree.bounding_box import make_bounding_box_dtype

    bbox_type, _ = make_bounding_box_dtype(ctx.devices[0], dim, coord_dtype)

    bbox = np.empty(1, bbox_type)
    for ax in axis_names:
        bbox["min_" + ax] = a
        bbox["max_" + ax] = b

    # tune max_particles_in_box to reconstruct the mesh
    # TODO: use points from FieldPlotter are used as target points for better
    # visuals
    print("building tree")
    from boxtree import TreeBuilder

    tb = TreeBuilder(ctx)
    tree, _ = tb(
        queue,
        particles=q_points,
        targets=q_points,
        bbox=bbox,
        max_particles_in_box=q_order**3 * 8 - 1,
        kind="adaptive-level-restricted",
    )

    from boxtree.traversal import FMMTraversalBuilder

    tg = FMMTraversalBuilder(ctx)
    trav, _ = tg(queue, tree)

    # }}} End build tree and traversals

    # {{{ build near field potential table

    from volumential.table_manager import NearFieldInteractionTableManager
    import os

    if download_table and (not os.path.isfile(table_filename)):
        import json
        with open("table_urls.json", 'r') as fp:
            urls = json.load(fp)

        print("Downloading table from %s" % urls['Laplace3D'])
        import subprocess
        subprocess.call(["wget", "-q", urls['Laplace3D'], table_filename])

    tm = NearFieldInteractionTableManager(table_filename,
                                          root_extent=root_table_source_extent,
                                          queue=queue)

    if use_multilevel_table:
        logger.info("Using multilevel tables")
        assert (abs(
            int((b - a) / root_table_source_extent) *
            root_table_source_extent - (b - a)) < 1e-15)
        nftable = []
        for lev in range(0, tree.nlevels + 1):
            print("Getting table at level", lev)
            tb, _ = tm.get_table(
                dim,
                "Laplace",
                q_order,
                source_box_level=lev,
                compute_method="DrosteSum",
                queue=queue,
                n_brick_quad_points=120,
                adaptive_level=False,
                use_symmetry=True,
                alpha=0,
                n_levels=1,
            )
            nftable.append(tb)

        print("Using table list of length", len(nftable))

    else:
        logger.info("Using single level table")
        force_recompute = False
        # 15 levels are sufficient (the inner most brick is 1e-15**3 in volume)
        nftable, _ = tm.get_table(
            dim,
            "Laplace",
            q_order,
            force_recompute=force_recompute,
            compute_method="DrosteSum",
            queue=queue,
            n_brick_quad_points=120,
            adaptive_level=False,
            use_symmetry=True,
            alpha=0,
            n_levels=1,
        )

    # }}} End build near field potential table

    # {{{ sumpy expansion for laplace kernel

    from sumpy.expansion import DefaultExpansionFactory
    from sumpy.kernel import LaplaceKernel

    knl = LaplaceKernel(dim)
    out_kernels = [knl]

    expn_factory = DefaultExpansionFactory()
    local_expn_class = expn_factory.get_local_expansion_class(knl)
    mpole_expn_class = expn_factory.get_multipole_expansion_class(knl)

    exclude_self = True
    from volumential.expansion_wrangler_fpnd import (
        FPNDExpansionWrangler, FPNDExpansionWranglerCodeContainer)

    wcc = FPNDExpansionWranglerCodeContainer(
        ctx,
        partial(mpole_expn_class, knl),
        partial(local_expn_class, knl),
        out_kernels,
        exclude_self=exclude_self,
    )

    if exclude_self:
        target_to_source = np.arange(tree.ntargets, dtype=np.int32)
        self_extra_kwargs = {"target_to_source": target_to_source}
    else:
        self_extra_kwargs = {}

    wrangler = FPNDExpansionWrangler(
        code_container=wcc,
        queue=queue,
        tree=tree,
        near_field_table=nftable,
        dtype=dtype,
        fmm_level_to_order=lambda kernel, kernel_args, tree, lev: m_order,
        quad_order=q_order,
        self_extra_kwargs=self_extra_kwargs,
    )

    # }}} End sumpy expansion for laplace kernel

    print("*************************")
    print("* Performing FMM ...")
    print("*************************")

    # {{{ conduct fmm computation

    from volumential.volume_fmm import drive_volume_fmm

    import time
    queue.finish()

    t0 = time.time()

    pot, = drive_volume_fmm(trav,
                            wrangler,
                            source_vals * q_weights,
                            source_vals,
                            direct_evaluation=force_direct_evaluation,
                            list1_only=False)

    t1 = time.time()

    print("Finished in %.2f seconds." % (t1 - t0))
    print("(%e points per second)" % (len(q_weights) / (t1 - t0)))

    # }}} End conduct fmm computation

    print("*************************")
    print("* Postprocessing ...")
    print("*************************")

    # {{{ postprocess and plot

    # print(pot)

    solu_eval = Eval(dim, solu_expr, [x, y, z])
    # x = q_points[0].get()
    # y = q_points[1].get()
    # z = q_points[2].get()
    test_x = np.array([0.0])
    test_y = np.array([0.0])
    test_z = np.array([0.0])
    test_nodes = make_obj_array(
        # get() first for CL compatibility issues
        [
            cl.array.to_device(queue, test_x),
            cl.array.to_device(queue, test_y),
            cl.array.to_device(queue, test_z),
        ])

    from volumential.volume_fmm import interpolate_volume_potential

    ze = solu_eval(queue, np.array([test_x, test_y, test_z]))
    zs = interpolate_volume_potential(test_nodes, trav, wrangler, pot).get()

    print_error = True
    if print_error:
        err = np.max(np.abs(ze - zs))
        print("Error =", err)

    # Boxtree
    if 0:
        import matplotlib.pyplot as plt

        if dim == 2:
            plt.plot(q_points[0].get(), q_points[1].get(), ".")

        from boxtree.visualization import TreePlotter

        plotter = TreePlotter(tree.get(queue=queue))
        plotter.draw_tree(fill=False, edgecolor="black")
        # plotter.draw_box_numbers()
        plotter.set_bounding_box()
        plt.gca().set_aspect("equal")

        plt.draw()
        plt.show()
        # plt.savefig("tree.png")

    # Direct p2p

    if 0:
        print("Performing P2P")
        pot_direct, = drive_volume_fmm(trav,
                                       wrangler,
                                       source_vals * q_weights,
                                       source_vals,
                                       direct_evaluation=True)
        zds = pot_direct.get()
        zs = pot.get()

        print("P2P-FMM diff =", np.max(np.abs(zs - zds)))

        print("P2P Error =", np.max(np.abs(ze - zds)))

    # Write vtk
    if 0:
        from meshmode.mesh.io import read_gmsh

        modemesh = read_gmsh("box_grid.msh", force_ambient_dim=None)
        from meshmode.discretization.poly_element import (
            LegendreGaussLobattoTensorProductGroupFactory, )
        from meshmode.array_context import PyOpenCLArrayContext
        from meshmode.discretization import Discretization

        actx = PyOpenCLArrayContext(queue)
        box_discr = Discretization(
            actx, modemesh,
            LegendreGaussLobattoTensorProductGroupFactory(q_order))

        box_nodes_x = box_discr.nodes()[0].with_queue(queue).get()
        box_nodes_y = box_discr.nodes()[1].with_queue(queue).get()
        box_nodes_z = box_discr.nodes()[2].with_queue(queue).get()
        box_nodes = make_obj_array(
            # get() first for CL compatibility issues
            [
                cl.array.to_device(queue, box_nodes_x),
                cl.array.to_device(queue, box_nodes_y),
                cl.array.to_device(queue, box_nodes_z),
            ])

        visual_order = 1
        from meshmode.discretization.visualization import make_visualizer

        vis = make_visualizer(queue, box_discr, visual_order)

        from volumential.volume_fmm import interpolate_volume_potential

        volume_potential = interpolate_volume_potential(
            box_nodes, trav, wrangler, pot)

        # qx = q_points[0].get()
        # qy = q_points[1].get()
        # qz = q_points[2].get()
        exact_solution = cl.array.to_device(
            queue,
            solu_eval(queue, np.array([box_nodes_x, box_nodes_y,
                                       box_nodes_z])))

        # clean up the mess
        def clean_file(filename):
            import os

            try:
                os.remove(filename)
            except OSError:
                pass

        vtu_filename = "laplace3d.vtu"
        clean_file(vtu_filename)
        vis.write_vtk_file(
            vtu_filename,
            [
                ("VolPot", volume_potential),
                # ("SrcDensity", source_density),
                ("ExactSol", exact_solution),
                ("Error", volume_potential - exact_solution),
            ],
        )
        print("Written file " + vtu_filename)