Esempio n. 1
0
    def get_operator(self, ambient_dim, qbx_forced_limit="avg"):
        knl = self.knl_class(ambient_dim)
        kwargs = self.knl_sym_kwargs.copy()
        kwargs["qbx_forced_limit"] = qbx_forced_limit

        if self.op_type == "scalar":
            sym_u = sym.var("u")
            sym_op = sym.S(knl, sym_u, **kwargs)
        elif self.op_type == "scalar_mixed":
            sym_u = sym.var("u")
            sym_op = sym.S(knl, 0.3 * sym_u, **kwargs) \
                    + sym.D(knl, 0.5 * sym_u, **kwargs)
        elif self.op_type == "vector":
            sym_u = sym.make_sym_vector("u", ambient_dim)

            sym_op = make_obj_array([
                sym.Sp(knl, sym_u[0], **kwargs) +
                sym.D(knl, sym_u[1], **kwargs),
                sym.S(knl, 0.4 * sym_u[0], **kwargs) +
                0.3 * sym.D(knl, sym_u[0], **kwargs)
            ])
        else:
            raise ValueError(f"unknown operator type: '{self.op_type}'")

        sym_op = 0.5 * sym_u + sym_op
        return sym_u, sym_op
def main():
    cl_ctx = cl.create_some_context()
    queue = cl.CommandQueue(cl_ctx)

    target_order = 10

    from functools import partial
    nelements = 30
    qbx_order = 4

    from sumpy.kernel import LaplaceKernel
    from meshmode.mesh.generation import (  # noqa
            ellipse, cloverleaf, starfish, drop, n_gon, qbx_peanut,
            make_curve_mesh)
    mesh = make_curve_mesh(partial(ellipse, 1),
            np.linspace(0, 1, nelements+1),
            target_order)

    from meshmode.discretization import Discretization
    from meshmode.discretization.poly_element import \
            InterpolatoryQuadratureSimplexGroupFactory

    density_discr = Discretization(cl_ctx, mesh,
            InterpolatoryQuadratureSimplexGroupFactory(target_order))

    from pytential.qbx import QBXLayerPotentialSource
    qbx = QBXLayerPotentialSource(density_discr, 4*target_order,
            qbx_order, fmm_order=False)

    from pytools.obj_array import join_fields
    sig_sym = sym.var("sig")
    knl = LaplaceKernel(2)
    op = join_fields(
            sym.tangential_derivative(mesh.ambient_dim,
                sym.D(knl, sig_sym, qbx_forced_limit=+1)).as_scalar(),
            sym.tangential_derivative(mesh.ambient_dim,
                sym.D(knl, sig_sym, qbx_forced_limit=-1)).as_scalar(),
            )

    nodes = density_discr.nodes().with_queue(queue)
    angle = cl.clmath.atan2(nodes[1], nodes[0])
    n = 10
    sig = cl.clmath.sin(n*angle)
    dt_sig = n*cl.clmath.cos(n*angle)

    res = bind(qbx, op)(queue, sig=sig)

    extval = res[0].get()
    intval = res[1].get()
    pv = 0.5*(extval + intval)

    dt_sig_h = dt_sig.get()

    import matplotlib.pyplot as pt
    pt.plot(extval, label="+num")
    pt.plot(pv + dt_sig_h*0.5, label="+ex")
    pt.legend(loc="best")
    pt.show()
Esempio n. 3
0
    def representation(self, u,
            map_potentials=None, qbx_forced_limit=None, **kwargs):
        """
        :param u: symbolic variable for the density.
        :param map_potentials: a callable that can be used to apply
            additional transformations on all the layer potentials in the
            representation, e.g. to take a target derivative.
        """
        sqrt_w = self.get_sqrt_weight()
        inv_sqrt_w_u = sym.cse(u/sqrt_w)
        laplace_s_inv_sqrt_w_u = sym.cse(sym.S(self.laplace_kernel, inv_sqrt_w_u))

        if map_potentials is None:
            def map_potentials(x):  # pylint:disable=function-redefined
                return x

        kwargs["qbx_forced_limit"] = qbx_forced_limit
        kwargs["kernel_arguments"] = self.kernel_arguments

        return (
                map_potentials(
                    sym.S(self.kernel, inv_sqrt_w_u, **kwargs)
                    )
                - self.alpha * map_potentials(
                    sym.D(self.kernel, laplace_s_inv_sqrt_w_u, **kwargs)
                    )
                )
Esempio n. 4
0
def test_cost_model(ctx_factory, dim, use_target_specific_qbx):
    """Test that cost model gathering can execute successfully."""
    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue)

    lpot_source = get_lpot_source(actx, dim).copy(
            _use_target_specific_qbx=use_target_specific_qbx,
            cost_model=CostModel())
    places = GeometryCollection(lpot_source)

    density_discr = places.get_discretization(places.auto_source.geometry)
    sigma = get_density(actx, density_discr)

    sigma_sym = sym.var("sigma")
    k_sym = LaplaceKernel(lpot_source.ambient_dim)

    sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1)
    op_S = bind(places, sym_op_S)
    cost_S = op_S.get_modeled_cost(actx, sigma=sigma)
    assert len(cost_S) == 1

    sym_op_S_plus_D = (
            sym.S(k_sym, sigma_sym, qbx_forced_limit=+1)
            + sym.D(k_sym, sigma_sym, qbx_forced_limit="avg"))
    op_S_plus_D = bind(places, sym_op_S_plus_D)
    cost_S_plus_D = op_S_plus_D.get_modeled_cost(actx, sigma=sigma)
    assert len(cost_S_plus_D) == 2
Esempio n. 5
0
    def operator(self, u):
        sqrt_w = self.get_sqrt_weight()
        inv_sqrt_w_u = cse(u / sqrt_w)

        if self.is_unique_only_up_to_constant():
            # The exterior Dirichlet operator in this representation
            # has a nullspace. The mean of the density must be matched
            # to the desired solution separately. As is, this operator
            # returns a mean that is not well-specified.
            #
            # See Hackbusch, https://books.google.com/books?id=Ssnf7SZB0ZMC
            # Theorem 8.2.18b

            amb_dim = self.kernel.dim
            ones_contribution = (sym.Ones() *
                                 sym.mean(amb_dim, amb_dim - 1, inv_sqrt_w_u))
        else:
            ones_contribution = 0

        return (
            -self.loc_sign * 0.5 * u + sqrt_w *
            (self.alpha * sym.S(self.kernel,
                                inv_sqrt_w_u,
                                qbx_forced_limit=+1,
                                kernel_arguments=self.kernel_arguments) -
             sym.D(self.kernel,
                   inv_sqrt_w_u,
                   qbx_forced_limit="avg",
                   kernel_arguments=self.kernel_arguments) + ones_contribution)
        )
Esempio n. 6
0
def test_unregularized_off_surface_fmm_vs_direct(ctx_factory):
    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)
    actx = PyOpenCLArrayContext(queue)

    nelements = 300
    target_order = 8
    fmm_order = 4

    # {{{ geometry

    mesh = make_curve_mesh(WobblyCircle.random(8, seed=30),
                np.linspace(0, 1, nelements+1),
                target_order)

    from pytential.unregularized import UnregularizedLayerPotentialSource
    from meshmode.discretization import Discretization
    from meshmode.discretization.poly_element import \
            InterpolatoryQuadratureSimplexGroupFactory

    density_discr = Discretization(
            actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))
    direct = UnregularizedLayerPotentialSource(
            density_discr,
            fmm_order=False,
            )
    fmm = direct.copy(
            fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order)

    sigma = density_discr.zeros(actx) + 1

    fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100)
    from pytential.target import PointsTarget
    ptarget = PointsTarget(fplot.points)

    from pytential import GeometryCollection
    places = GeometryCollection({
        "unregularized_direct": direct,
        "unregularized_fmm": fmm,
        "targets": ptarget})

    # }}}

    # {{{ check

    from sumpy.kernel import LaplaceKernel
    op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None)

    direct_fld_in_vol = bind(places, op,
            auto_where=("unregularized_direct", "targets"))(
                    actx, sigma=sigma)
    fmm_fld_in_vol = bind(places, op,
            auto_where=("unregularized_fmm", "targets"))(actx, sigma=sigma)

    err = actx.np.fabs(fmm_fld_in_vol - direct_fld_in_vol)
    linf_err = actx.to_numpy(err).max()
    print("l_inf error:", linf_err)

    assert linf_err < 5e-3
Esempio n. 7
0
def test_off_surface_eval(actx_factory, use_fmm, visualize=False):
    logging.basicConfig(level=logging.INFO)

    actx = actx_factory()

    # prevent cache 'splosion
    from sympy.core.cache import clear_cache
    clear_cache()

    nelements = 30
    target_order = 8
    qbx_order = 3
    if use_fmm:
        fmm_order = qbx_order
    else:
        fmm_order = False

    mesh = mgen.make_curve_mesh(partial(mgen.ellipse, 3),
                                np.linspace(0, 1, nelements + 1), target_order)

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

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

    from pytential.target import PointsTarget
    fplot = FieldPlotter(np.zeros(2), extent=0.54, npoints=30)
    targets = PointsTarget(actx.freeze(actx.from_numpy(fplot.points)))

    places = GeometryCollection((qbx, targets))
    density_discr = places.get_discretization(places.auto_source.geometry)

    from sumpy.kernel import LaplaceKernel
    op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=-2)

    sigma = density_discr.zeros(actx) + 1
    fld_in_vol = bind(places, op)(actx, sigma=sigma)
    fld_in_vol_exact = -1

    linf_err = actx.to_numpy(
        actx.np.linalg.norm(fld_in_vol - fld_in_vol_exact, ord=np.inf))
    logger.info("l_inf error: %.12e", linf_err)

    if visualize:
        fplot.show_scalar_in_matplotlib(actx.to_numpy(fld_in_vol))
        import matplotlib.pyplot as pt
        pt.colorbar()
        pt.show()

    assert linf_err < 1e-3
Esempio n. 8
0
def test_off_surface_eval(ctx_factory, use_fmm, do_plot=False):
    logging.basicConfig(level=logging.INFO)

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

    # prevent cache 'splosion
    from sympy.core.cache import clear_cache
    clear_cache()

    nelements = 30
    target_order = 8
    qbx_order = 3
    if use_fmm:
        fmm_order = qbx_order
    else:
        fmm_order = False

    mesh = make_curve_mesh(partial(ellipse, 3),
                           np.linspace(0, 1, nelements + 1), target_order)

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

    pre_density_discr = Discretization(
        cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))
    qbx, _ = QBXLayerPotentialSource(
        pre_density_discr,
        4 * target_order,
        qbx_order,
        fmm_order=fmm_order,
    ).with_refinement()

    density_discr = qbx.density_discr

    from sumpy.kernel import LaplaceKernel
    op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=-2)

    sigma = density_discr.zeros(queue) + 1

    fplot = FieldPlotter(np.zeros(2), extent=0.54, npoints=30)
    from pytential.target import PointsTarget
    fld_in_vol = bind((qbx, PointsTarget(fplot.points)), op)(queue,
                                                             sigma=sigma)

    err = cl.clmath.fabs(fld_in_vol - (-1))

    linf_err = cl.array.max(err).get()
    print("l_inf error:", linf_err)

    if do_plot:
        fplot.show_scalar_in_matplotlib(fld_in_vol.get())
        import matplotlib.pyplot as pt
        pt.colorbar()
        pt.show()

    assert linf_err < 1e-3
Esempio n. 9
0
def test_expr_pickling():
    import pickle
    from sumpy.kernel import LaplaceKernel, AxisTargetDerivative

    ops_for_testing = [
        sym.d_dx(
            2, sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=-2)),
        sym.D(AxisTargetDerivative(0, LaplaceKernel(2)),
              sym.var("sigma"),
              qbx_forced_limit=-2)
    ]

    for op in ops_for_testing:
        pickled_op = pickle.dumps(op)
        after_pickle_op = pickle.loads(pickled_op)

        assert op == after_pickle_op
    def get_zero_op(self, kernel, **knl_kwargs):

        u_sym = sym.var("u")
        dn_u_sym = sym.var("dn_u")

        return (sym.S(kernel, dn_u_sym, qbx_forced_limit=-1, **knl_kwargs) -
                sym.D(kernel, u_sym, qbx_forced_limit="avg", **knl_kwargs) -
                0.5 * u_sym)
Esempio n. 11
0
def _build_op(lpot_id,
              k=0,
              ndim=2,
              source=sym.DEFAULT_SOURCE,
              target=sym.DEFAULT_TARGET,
              qbx_forced_limit="avg"):

    from sumpy.kernel import LaplaceKernel, HelmholtzKernel
    if k:
        knl = HelmholtzKernel(ndim)
        knl_kwargs = {"k": k}
    else:
        knl = LaplaceKernel(ndim)
        knl_kwargs = {}

    lpot_kwargs = {
            "qbx_forced_limit": qbx_forced_limit,
            "source": source,
            "target": target}
    lpot_kwargs.update(knl_kwargs)
    if lpot_id == 1:
        # scalar single-layer potential
        u_sym = sym.var("u")
        op = sym.S(knl, u_sym, **lpot_kwargs)
    elif lpot_id == 2:
        # scalar combination of layer potentials
        u_sym = sym.var("u")
        op = sym.S(knl, 0.3 * u_sym, **lpot_kwargs) \
             + sym.D(knl, 0.5 * u_sym, **lpot_kwargs)
    elif lpot_id == 3:
        # vector potential
        u_sym = sym.make_sym_vector("u", 2)
        u0_sym, u1_sym = u_sym

        op = make_obj_array([
            sym.Sp(knl, u0_sym, **lpot_kwargs)
            + sym.D(knl, u1_sym, **lpot_kwargs),
            sym.S(knl, 0.4 * u0_sym, **lpot_kwargs)
            + 0.3 * sym.D(knl, u0_sym, **lpot_kwargs)
            ])
    else:
        raise ValueError("Unknown lpot_id: {}".format(lpot_id))

    op = 0.5 * u_sym + op

    return op, u_sym, knl_kwargs
    def get_zero_op(self, kernel, **knl_kwargs):
        d = kernel.dim
        u_sym = sym.var("u")
        grad_u_sym = sym.make_sym_mv("grad_u", d)
        dn_u_sym = sym.var("dn_u")

        return (d1.resolve(
            d1.dnabla(d) *
            d1(sym.S(kernel, dn_u_sym, qbx_forced_limit="avg", **knl_kwargs))
        ) - d2.resolve(
            d2.dnabla(d) *
            d2(sym.D(kernel, u_sym, qbx_forced_limit="avg", **knl_kwargs))) -
                0.5 * grad_u_sym)
Esempio n. 13
0
def get_lpot_cost(which, helmholtz_k, geometry_getter, lpot_kwargs, kind):
    """
    Parameters:

        which: "D" or "S"
        kind: "actual" or "model"
    """
    context = cl.create_some_context(interactive=False)
    queue = cl.CommandQueue(context)

    lpot_source = geometry_getter(queue, lpot_kwargs)

    from sumpy.kernel import LaplaceKernel, HelmholtzKernel
    sigma_sym = sym.var("sigma")
    if helmholtz_k == 0:
        k_sym = LaplaceKernel(lpot_source.ambient_dim)
        kernel_kwargs = {}
    else:
        k_sym = HelmholtzKernel(lpot_source.ambient_dim, "k")
        kernel_kwargs = {"k": helmholtz_k}

    if which == "S":
        op = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1, **kernel_kwargs)
    elif which == "D":
        op = sym.D(k_sym, sigma_sym, qbx_forced_limit="avg", **kernel_kwargs)
    else:
        raise ValueError("unknown lpot symbol: '%s'" % which)

    bound_op = bind(lpot_source, op)

    density_discr = lpot_source.density_discr
    nodes = density_discr.nodes().with_queue(queue)
    sigma = cl.clmath.sin(10 * nodes[0])

    if kind == "actual":
        timing_data = {}
        result = bound_op.eval(queue, {"sigma": sigma},
                               timing_data=timing_data)
        assert not np.isnan(result.get(queue)).any()
        result = one(timing_data.values())

    elif kind == "model":
        perf_results = bound_op.get_modeled_performance(queue, sigma=sigma)
        result = one(perf_results.values())

    return result
Esempio n. 14
0
def test_unregularized_off_surface_fmm_vs_direct(ctx_factory):
    cl_ctx = ctx_factory()
    queue = cl.CommandQueue(cl_ctx)

    nelements = 300
    target_order = 8
    fmm_order = 4

    mesh = make_curve_mesh(WobblyCircle.random(8, seed=30),
                           np.linspace(0, 1, nelements + 1), target_order)

    from pytential.unregularized import UnregularizedLayerPotentialSource
    from meshmode.discretization import Discretization
    from meshmode.discretization.poly_element import \
            InterpolatoryQuadratureSimplexGroupFactory

    density_discr = Discretization(
        cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))
    direct = UnregularizedLayerPotentialSource(
        density_discr,
        fmm_order=False,
    )
    fmm = direct.copy(
        fmm_level_to_order=lambda kernel, kernel_args, tree, level: fmm_order)

    sigma = density_discr.zeros(queue) + 1

    fplot = FieldPlotter(np.zeros(2), extent=5, npoints=100)
    from pytential.target import PointsTarget
    ptarget = PointsTarget(fplot.points)
    from sumpy.kernel import LaplaceKernel

    op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None)

    direct_fld_in_vol = bind((direct, ptarget), op)(queue, sigma=sigma)
    fmm_fld_in_vol = bind((fmm, ptarget), op)(queue, sigma=sigma)

    err = cl.clmath.fabs(fmm_fld_in_vol - direct_fld_in_vol)

    linf_err = cl.array.max(err).get()
    print("l_inf error:", linf_err)
    assert linf_err < 5e-3
Esempio n. 15
0
    def representation(self,
                       u,
                       map_potentials=None,
                       qbx_forced_limit=None,
                       **kwargs):
        sqrt_w = self.get_sqrt_weight()
        inv_sqrt_w_u = cse(u / sqrt_w)

        if map_potentials is None:

            def map_potentials(x):  # pylint:disable=function-redefined
                return x

        kwargs["qbx_forced_limit"] = qbx_forced_limit
        kwargs["kernel_arguments"] = self.kernel_arguments

        return (map_potentials(sym.S(self.kernel, inv_sqrt_w_u, **kwargs)) -
                self.alpha * map_potentials(
                    sym.D(self.kernel, sym.S(self.laplace_kernel,
                                             inv_sqrt_w_u), **kwargs)))
Esempio n. 16
0
def test_cost_model(actx_factory, dim, use_target_specific_qbx, per_box):
    """Test that cost model gathering can execute successfully."""
    actx = actx_factory()

    lpot_source = get_lpot_source(actx, dim).copy(
        _use_target_specific_qbx=use_target_specific_qbx,
        cost_model=QBXCostModel())
    places = GeometryCollection(lpot_source)

    density_discr = places.get_discretization(places.auto_source.geometry)
    sigma = get_density(actx, density_discr)

    sigma_sym = sym.var("sigma")
    k_sym = LaplaceKernel(lpot_source.ambient_dim)

    sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1)
    op_S = bind(places, sym_op_S)

    if per_box:
        cost_S, _ = op_S.cost_per_box("constant_one", sigma=sigma)
    else:
        cost_S, _ = op_S.cost_per_stage("constant_one", sigma=sigma)

    assert len(cost_S) == 1

    sym_op_S_plus_D = (sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) +
                       sym.D(k_sym, sigma_sym, qbx_forced_limit="avg"))
    op_S_plus_D = bind(places, sym_op_S_plus_D)

    if per_box:
        cost_S_plus_D, _ = op_S_plus_D.cost_per_box("constant_one",
                                                    sigma=sigma)
    else:
        cost_S_plus_D, _ = op_S_plus_D.cost_per_stage("constant_one",
                                                      sigma=sigma)

    assert len(cost_S_plus_D) == 2
Esempio n. 17
0
def test_cost_model(ctx_getter, dim, use_target_specific_qbx):
    """Test that cost model gathering can execute successfully."""
    cl_ctx = ctx_getter()
    queue = cl.CommandQueue(cl_ctx)

    lpot_source = (get_lpot_source(queue, dim).copy(
        _use_target_specific_qbx=use_target_specific_qbx,
        cost_model=CostModel()))

    sigma = get_density(queue, lpot_source)

    sigma_sym = sym.var("sigma")
    k_sym = LaplaceKernel(lpot_source.ambient_dim)

    sym_op_S = sym.S(k_sym, sigma_sym, qbx_forced_limit=+1)
    op_S = bind(lpot_source, sym_op_S)
    cost_S = op_S.get_modeled_cost(queue, sigma=sigma)
    assert len(cost_S) == 1

    sym_op_S_plus_D = (sym.S(k_sym, sigma_sym, qbx_forced_limit=+1) +
                       sym.D(k_sym, sigma_sym))
    op_S_plus_D = bind(lpot_source, sym_op_S_plus_D)
    cost_S_plus_D = op_S_plus_D.get_modeled_cost(queue, sigma=sigma)
    assert len(cost_S_plus_D) == 2
Esempio n. 18
0
def nonlocal_integral_eq(
    mesh,
    scatterer_bdy_id,
    outer_bdy_id,
    wave_number,
    options_prefix=None,
    solver_parameters=None,
    fspace=None,
    vfspace=None,
    true_sol_grad_expr=None,
    actx=None,
    dgfspace=None,
    dgvfspace=None,
    meshmode_src_connection=None,
    qbx_kwargs=None,
):
    r"""
        see run_method for descriptions of unlisted args

        args:

        gamma and beta are used to precondition
        with the following equation:

        \Delta u - \kappa^2 \gamma u = 0
        (\partial_n - i\kappa\beta) u |_\Sigma = 0
    """
    # make sure we get outer bdy id as tuple in case it consists of multiple ids
    if isinstance(outer_bdy_id, int):
        outer_bdy_id = [outer_bdy_id]
    outer_bdy_id = tuple(outer_bdy_id)
    # away from the excluded region, but firedrake and meshmode point
    # into
    pyt_inner_normal_sign = -1

    ambient_dim = mesh.geometric_dimension()

    # {{{ Build src and tgt

    # build connection meshmode near src boundary -> src boundary inside meshmode
    from meshmode.discretization.poly_element import \
        InterpolatoryQuadratureSimplexGroupFactory
    from meshmode.discretization.connection import make_face_restriction
    factory = InterpolatoryQuadratureSimplexGroupFactory(
        dgfspace.finat_element.degree)
    src_bdy_connection = make_face_restriction(actx,
                                               meshmode_src_connection.discr,
                                               factory, scatterer_bdy_id)
    # source is a qbx layer potential
    from pytential.qbx import QBXLayerPotentialSource
    disable_refinement = (fspace.mesh().geometric_dimension() == 3)
    qbx = QBXLayerPotentialSource(src_bdy_connection.to_discr,
                                  **qbx_kwargs,
                                  _disable_refinement=disable_refinement)

    # get target indices and point-set
    target_indices, target = get_target_points_and_indices(
        fspace, outer_bdy_id)

    # }}}

    # build the operations
    from pytential import bind, sym
    r"""
    ..math:

    x \in \Sigma

    grad_op(x) =
        \nabla(
            \int_\Gamma(
                u(y) \partial_n H_0^{(1)}(\kappa |x - y|)
            )d\gamma(y)
        )
    """
    grad_op = pyt_inner_normal_sign * sym.grad(
        ambient_dim,
        sym.D(HelmholtzKernel(ambient_dim),
              sym.var("u"),
              k=sym.var("k"),
              qbx_forced_limit=None))
    r"""
    ..math:

    x \in \Sigma

    op(x) =
        i \kappa \cdot
        \int_\Gamma(
            u(y) \partial_n H_0^{(1)}(\kappa |x - y|)
        )d\gamma(y)
    """
    op = pyt_inner_normal_sign * 1j * sym.var("k") * (sym.D(
        HelmholtzKernel(ambient_dim),
        sym.var("u"),
        k=sym.var("k"),
        qbx_forced_limit=None))

    # bind the operations
    pyt_grad_op = bind((qbx, target), grad_op)
    pyt_op = bind((qbx, target), op)

    # }}}

    class MatrixFreeB(object):
        def __init__(self, A, pyt_grad_op, pyt_op, actx, kappa):
            """
            :arg kappa: The wave number
            """

            self.actx = actx
            self.k = kappa
            self.pyt_op = pyt_op
            self.pyt_grad_op = pyt_grad_op
            self.A = A
            self.meshmode_src_connection = meshmode_src_connection

            # {{{ Create some functions needed for multing
            self.x_fntn = Function(fspace)

            # CG
            self.potential_int = Function(fspace)
            self.potential_int.dat.data[:] = 0.0
            self.grad_potential_int = Function(vfspace)
            self.grad_potential_int.dat.data[:] = 0.0
            self.pyt_result = Function(fspace)

            self.n = FacetNormal(mesh)
            self.v = TestFunction(fspace)

            # some meshmode ones
            self.x_mm_fntn = self.meshmode_src_connection.discr.empty(
                self.actx, dtype='c')

            # }}}

        def mult(self, mat, x, y):
            # Copy function data into the fivredrake function
            self.x_fntn.dat.data[:] = x[:]
            # Transfer the function to meshmode
            self.meshmode_src_connection.from_firedrake(project(
                self.x_fntn, dgfspace),
                                                        out=self.x_mm_fntn)
            # Restrict to boundary
            x_mm_fntn_on_bdy = src_bdy_connection(self.x_mm_fntn)

            # Apply the operation
            potential_int_mm = self.pyt_op(self.actx,
                                           u=x_mm_fntn_on_bdy,
                                           k=self.k)
            grad_potential_int_mm = self.pyt_grad_op(self.actx,
                                                     u=x_mm_fntn_on_bdy,
                                                     k=self.k)
            # Store in firedrake
            self.potential_int.dat.data[target_indices] = potential_int_mm.get(
            )
            for dim in range(grad_potential_int_mm.shape[0]):
                self.grad_potential_int.dat.data[
                    target_indices, dim] = grad_potential_int_mm[dim].get()

            # Integrate the potential
            r"""
            Compute the inner products using firedrake. Note this
            will be subtracted later, hence appears off by a sign.

            .. math::

                \langle
                    n(x) \cdot \nabla(
                        \int_\Gamma(
                            u(y) \partial_n H_0^{(1)}(\kappa |x - y|)
                        )d\gamma(y)
                    ), v
                \rangle_\Sigma
                - \langle
                    i \kappa \cdot
                    \int_\Gamma(
                        u(y) \partial_n H_0^{(1)}(\kappa |x - y|)
                    )d\gamma(y), v
                \rangle_\Sigma
            """
            self.pyt_result = assemble(
                inner(inner(self.grad_potential_int, self.n), self.v) *
                ds(outer_bdy_id) -
                inner(self.potential_int, self.v) * ds(outer_bdy_id))

            # y <- Ax - evaluated potential
            self.A.mult(x, y)
            with self.pyt_result.dat.vec_ro as ep:
                y.axpy(-1, ep)

    # {{{ Compute normal helmholtz operator
    u = TrialFunction(fspace)
    v = TestFunction(fspace)
    r"""
    .. math::

        \langle
            \nabla u, \nabla v
        \rangle
        - \kappa^2 \cdot \langle
            u, v
        \rangle
        - i \kappa \langle
            u, v
        \rangle_\Sigma
    """
    a = inner(grad(u), grad(v)) * dx \
        - Constant(wave_number**2) * inner(u, v) * dx \
        - Constant(1j * wave_number) * inner(u, v) * ds(outer_bdy_id)

    # get the concrete matrix from a general bilinear form
    A = assemble(a).M.handle
    # }}}

    # {{{ Setup Python matrix
    B = PETSc.Mat().create()

    # build matrix context
    Bctx = MatrixFreeB(A, pyt_grad_op, pyt_op, actx, wave_number)

    # set up B as same size as A
    B.setSizes(*A.getSizes())

    B.setType(B.Type.PYTHON)
    B.setPythonContext(Bctx)
    B.setUp()
    # }}}

    # {{{ Create rhs

    # Remember f is \partial_n(true_sol)|_\Gamma
    # so we just need to compute \int_\Gamma\partial_n(true_sol) H(x-y)

    sigma = sym.make_sym_vector("sigma", ambient_dim)
    r"""
    ..math:

    x \in \Sigma

    grad_op(x) =
        \nabla(
            \int_\Gamma(
                f(y) H_0^{(1)}(\kappa |x - y|)
            )d\gamma(y)
        )
    """
    grad_op = pyt_inner_normal_sign * \
        sym.grad(ambient_dim, sym.S(HelmholtzKernel(ambient_dim),
                                    sym.n_dot(sigma),
                                    k=sym.var("k"), qbx_forced_limit=None))
    r"""
    ..math:

    x \in \Sigma

    op(x) =
        i \kappa \cdot
        \int_\Gamma(
            f(y) H_0^{(1)}(\kappa |x - y|)
        )d\gamma(y)
        )
    """
    op = 1j * sym.var("k") * pyt_inner_normal_sign * \
        sym.S(HelmholtzKernel(ambient_dim),
              sym.n_dot(sigma),
              k=sym.var("k"),
              qbx_forced_limit=None)

    rhs_grad_op = bind((qbx, target), grad_op)
    rhs_op = bind((qbx, target), op)

    # Transfer to meshmode
    metadata = {'quadrature_degree': 2 * fspace.ufl_element().degree()}
    dg_true_sol_grad = project(true_sol_grad_expr,
                               dgvfspace,
                               form_compiler_parameters=metadata)
    true_sol_grad_mm = meshmode_src_connection.from_firedrake(dg_true_sol_grad,
                                                              actx=actx)
    true_sol_grad_mm = src_bdy_connection(true_sol_grad_mm)
    # Apply the operations
    f_grad_convoluted_mm = rhs_grad_op(actx,
                                       sigma=true_sol_grad_mm,
                                       k=wave_number)
    f_convoluted_mm = rhs_op(actx, sigma=true_sol_grad_mm, k=wave_number)
    # Transfer function back to firedrake
    f_grad_convoluted = Function(vfspace)
    f_convoluted = Function(fspace)
    f_grad_convoluted.dat.data[:] = 0.0
    f_convoluted.dat.data[:] = 0.0

    for dim in range(f_grad_convoluted_mm.shape[0]):
        f_grad_convoluted.dat.data[target_indices,
                                   dim] = f_grad_convoluted_mm[dim].get()
    f_convoluted.dat.data[target_indices] = f_convoluted_mm.get()
    r"""
        \langle
            f, v
        \rangle_\Gamma
        + \langle
            i \kappa \cdot \int_\Gamma(
                f(y) H_0^{(1)}(\kappa |x - y|)
            )d\gamma(y), v
        \rangle_\Sigma
        - \langle
            n(x) \cdot \nabla(
                \int_\Gamma(
                    f(y) H_0^{(1)}(\kappa |x - y|)
                )d\gamma(y)
            ), v
        \rangle_\Sigma
    """
    rhs_form = inner(inner(true_sol_grad_expr, FacetNormal(mesh)),
                     v) * ds(scatterer_bdy_id, metadata=metadata) \
        + inner(f_convoluted, v) * ds(outer_bdy_id) \
        - inner(inner(f_grad_convoluted, FacetNormal(mesh)),
                v) * ds(outer_bdy_id)

    rhs = assemble(rhs_form)

    # {{{ set up a solver:
    solution = Function(fspace, name="Computed Solution")

    #       {{{ Used for preconditioning
    if 'gamma' in solver_parameters or 'beta' in solver_parameters:
        gamma = complex(solver_parameters.pop('gamma', 1.0))

        import cmath
        beta = complex(solver_parameters.pop('beta', cmath.sqrt(gamma)))

        p = inner(grad(u), grad(v)) * dx \
            - Constant(wave_number**2 * gamma) * inner(u, v) * dx \
            - Constant(1j * wave_number * beta) * inner(u, v) * ds(outer_bdy_id)
        P = assemble(p).M.handle

    else:
        P = A
    #       }}}

    # Set up options to contain solver parameters:
    ksp = PETSc.KSP().create()
    if solver_parameters['pc_type'] == 'pyamg':
        del solver_parameters['pc_type']  # We are using the AMG preconditioner

        pyamg_tol = solver_parameters.get('pyamg_tol', None)
        if pyamg_tol is not None:
            pyamg_tol = float(pyamg_tol)
        pyamg_maxiter = solver_parameters.get('pyamg_maxiter', None)
        if pyamg_maxiter is not None:
            pyamg_maxiter = int(pyamg_maxiter)
        ksp.setOperators(B)
        ksp.setUp()
        pc = ksp.pc
        pc.setType(pc.Type.PYTHON)
        pc.setPythonContext(
            AMGTransmissionPreconditioner(wave_number,
                                          fspace,
                                          A,
                                          tol=pyamg_tol,
                                          maxiter=pyamg_maxiter,
                                          use_plane_waves=True))
    # Otherwise use regular preconditioner
    else:
        ksp.setOperators(B, P)

    options_manager = OptionsManager(solver_parameters, options_prefix)
    options_manager.set_from_options(ksp)

    import petsc4py.PETSc
    petsc4py.PETSc.Sys.popErrorHandler()
    with rhs.dat.vec_ro as b:
        with solution.dat.vec as x:
            ksp.solve(b, x)
    # }}}

    return ksp, solution
Esempio n. 19
0
 def D(density):  # noqa
     return sym.D(self.kernel,
                  density,
                  kernel_arguments=self.kernel_arguments,
                  qbx_forced_limit=qbx_forced_limit)
Esempio n. 20
0
    def op(**kwargs):
        kwargs.update(kernel_kwargs)

        #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), **kwargs))
        return sym.D(kernel, sym.var("sigma"), **kwargs)
Esempio n. 21
0
def main():
    import logging
    logger = logging.getLogger(__name__)
    logging.basicConfig(level=logging.WARNING)  # INFO for more progress info

    from meshmode.mesh.io import generate_gmsh, FileSource
    mesh = generate_gmsh(
            FileSource(cad_file_name), 2, order=2,
            other_options=["-string", "Mesh.CharacteristicLengthMax = %g;" % h])

    from meshmode.mesh.processing import perform_flips
    # Flip elements--gmsh generates inside-out geometry.
    mesh = perform_flips(mesh, np.ones(mesh.nelements))

    from meshmode.mesh.processing import find_bounding_box
    bbox_min, bbox_max = find_bounding_box(mesh)
    bbox_center = 0.5*(bbox_min+bbox_max)
    bbox_size = max(bbox_max-bbox_min) / 2

    logger.info("%d elements" % mesh.nelements)

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

    density_discr = Discretization(
            cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))

    qbx, _ = QBXLayerPotentialSource(density_discr, 4*target_order, qbx_order,
            fmm_order=qbx_order + 3,
            target_association_tolerance=0.15).with_refinement()

    nodes = density_discr.nodes().with_queue(queue)

    angle = cl.clmath.atan2(nodes[1], nodes[0])

    from pytential import bind, sym
    #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None))
    op = sym.D(kernel, sym.var("sigma"), qbx_forced_limit=None)
    #op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None)

    sigma = cl.clmath.cos(mode_nr*angle)
    if 0:
        sigma = 0*angle
        from random import randrange
        for i in range(5):
            sigma[randrange(len(sigma))] = 1

    if isinstance(kernel, HelmholtzKernel):
        sigma = sigma.astype(np.complex128)

    fplot = FieldPlotter(bbox_center, extent=3.5*bbox_size, npoints=150)

    from pytential.target import PointsTarget
    fld_in_vol = bind(
            (qbx, PointsTarget(fplot.points)),
            op)(queue, sigma=sigma, k=k).get()

    #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5)
    fplot.write_vtk_file(
            "potential-3d.vts",
            [
                ("potential", fld_in_vol)
                ]
            )

    bdry_normals = bind(
            density_discr,
            sym.normal(density_discr.ambient_dim))(queue).as_vector(dtype=object)

    from meshmode.discretization.visualization import make_visualizer
    bdry_vis = make_visualizer(queue, density_discr, target_order)

    bdry_vis.write_vtk_file("source-3d.vtu", [
        ("sigma", sigma),
        ("bdry_normals", bdry_normals),
        ])
Esempio n. 22
0
def main(mesh_name="ellipse", visualize=False):
    import logging
    logging.basicConfig(level=logging.INFO)  # INFO for more progress info

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

    from meshmode.mesh.generation import ellipse, make_curve_mesh
    from functools import partial

    if mesh_name == "ellipse":
        mesh = make_curve_mesh(partial(ellipse, 1),
                               np.linspace(0, 1, nelements + 1), mesh_order)
    elif mesh_name == "ellipse_array":
        base_mesh = make_curve_mesh(partial(ellipse, 1),
                                    np.linspace(0, 1, nelements + 1),
                                    mesh_order)

        from meshmode.mesh.processing import affine_map, merge_disjoint_meshes
        nx = 2
        ny = 2
        dx = 2 / nx
        meshes = [
            affine_map(base_mesh,
                       A=np.diag([dx * 0.25, dx * 0.25]),
                       b=np.array([dx * (ix - nx / 2), dx * (iy - ny / 2)]))
            for ix in range(nx) for iy in range(ny)
        ]

        mesh = merge_disjoint_meshes(meshes, single_group=True)

        if visualize:
            from meshmode.mesh.visualization import draw_curve
            draw_curve(mesh)
            import matplotlib.pyplot as plt
            plt.show()
    else:
        raise ValueError(f"unknown mesh name: {mesh_name}")

    pre_density_discr = Discretization(
        actx, mesh,
        InterpolatoryQuadratureSimplexGroupFactory(bdry_quad_order))

    from pytential.qbx import (QBXLayerPotentialSource,
                               QBXTargetAssociationFailedException)
    qbx = QBXLayerPotentialSource(pre_density_discr,
                                  fine_order=bdry_ovsmp_quad_order,
                                  qbx_order=qbx_order,
                                  fmm_order=fmm_order)

    from sumpy.visualization import FieldPlotter
    fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500)
    targets = actx.from_numpy(fplot.points)

    from pytential import GeometryCollection
    places = GeometryCollection(
        {
            "qbx":
            qbx,
            "qbx_high_target_assoc_tol":
            qbx.copy(target_association_tolerance=0.05),
            "targets":
            PointsTarget(targets)
        },
        auto_where="qbx")
    density_discr = places.get_discretization("qbx")

    # {{{ describe bvp

    from sumpy.kernel import LaplaceKernel, HelmholtzKernel
    kernel = HelmholtzKernel(2)

    sigma_sym = sym.var("sigma")
    sqrt_w = sym.sqrt_jac_q_weight(2)
    inv_sqrt_w_sigma = sym.cse(sigma_sym / sqrt_w)

    # Brakhage-Werner parameter
    alpha = 1j

    # -1 for interior Dirichlet
    # +1 for exterior Dirichlet
    loc_sign = +1

    k_sym = sym.var("k")
    bdry_op_sym = (
        -loc_sign * 0.5 * sigma_sym + sqrt_w *
        (alpha * sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit=+1)
         - sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, qbx_forced_limit="avg")))

    # }}}

    bound_op = bind(places, bdry_op_sym)

    # {{{ fix rhs and solve

    from meshmode.dof_array import thaw
    nodes = thaw(actx, density_discr.nodes())
    k_vec = np.array([2, 1])
    k_vec = k * k_vec / la.norm(k_vec, 2)

    def u_incoming_func(x):
        return actx.np.exp(1j * (x[0] * k_vec[0] + x[1] * k_vec[1]))

    bc = -u_incoming_func(nodes)

    bvp_rhs = bind(places, sqrt_w * sym.var("bc"))(actx, bc=bc)

    from pytential.solve import gmres
    gmres_result = gmres(bound_op.scipy_op(actx,
                                           sigma_sym.name,
                                           dtype=np.complex128,
                                           k=k),
                         bvp_rhs,
                         tol=1e-8,
                         progress=True,
                         stall_iterations=0,
                         hard_failure=True)

    # }}}

    # {{{ postprocess/visualize

    repr_kwargs = dict(source="qbx_high_target_assoc_tol",
                       target="targets",
                       qbx_forced_limit=None)
    representation_sym = (
        alpha * sym.S(kernel, inv_sqrt_w_sigma, k=k_sym, **repr_kwargs) -
        sym.D(kernel, inv_sqrt_w_sigma, k=k_sym, **repr_kwargs))

    u_incoming = u_incoming_func(targets)
    ones_density = density_discr.zeros(actx)
    for elem in ones_density:
        elem.fill(1)

    indicator = actx.to_numpy(
        bind(places, sym.D(LaplaceKernel(2), sigma_sym,
                           **repr_kwargs))(actx, sigma=ones_density))

    try:
        fld_in_vol = actx.to_numpy(
            bind(places, representation_sym)(actx,
                                             sigma=gmres_result.solution,
                                             k=k))
    except QBXTargetAssociationFailedException as e:
        fplot.write_vtk_file("helmholtz-dirichlet-failed-targets.vts",
                             [("failed", e.failed_target_flags.get(queue))])
        raise

    #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5)
    fplot.write_vtk_file("helmholtz-dirichlet-potential.vts", [
        ("potential", fld_in_vol),
        ("indicator", indicator),
        ("u_incoming", actx.to_numpy(u_incoming)),
    ])
Esempio n. 23
0
                              op.representation(sym.var("u")))(queue,
                                                               u=weighted_u,
                                                               k=case.k)
        except QBXTargetAssociationFailedException as e:
            fplot.write_vtk_file(
                "failed-targets.vts",
                [("failed_targets", e.failed_target_flags.get(queue))])
            raise

        from sumpy.kernel import LaplaceKernel
        ones_density = density_discr.zeros(queue)
        ones_density.fill(1)
        indicator = bind(
            (qbx_tgt_tol, PointsTarget(fplot.points)),
            -sym.D(LaplaceKernel(density_discr.ambient_dim),
                   sym.var("sigma"),
                   qbx_forced_limit=None))(queue, sigma=ones_density).get()

        solved_pot = solved_pot.get()

        true_pot = bind((point_source, PointsTarget(fplot.points)),
                        pot_src)(queue,
                                 charges=source_charges_dev,
                                 **concrete_knl_kwargs).get()

        #fplot.show_scalar_in_mayavi(solved_pot.real, max_val=5)
        if case.prob_side == "scat":
            fplot.write_vtk_file("potential-%s.vts" % resolution, [
                ("pot_scattered", solved_pot),
                ("pot_incoming", -true_pot),
                ("indicator", indicator),
Esempio n. 24
0
def test_off_surface_eval_vs_direct(ctx_factory,  do_plot=False):
    logging.basicConfig(level=logging.INFO)

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

    # prevent cache 'splosion
    from sympy.core.cache import clear_cache
    clear_cache()

    nelements = 300
    target_order = 8
    qbx_order = 3

    mesh = make_curve_mesh(WobblyCircle.random(8, seed=30),
                np.linspace(0, 1, nelements+1),
                target_order)

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

    pre_density_discr = Discretization(
            actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))
    direct_qbx = QBXLayerPotentialSource(
            pre_density_discr, 4*target_order, qbx_order,
            fmm_order=False,
            target_association_tolerance=0.05,
            )
    fmm_qbx = QBXLayerPotentialSource(
            pre_density_discr, 4*target_order, qbx_order,
            fmm_order=qbx_order + 3,
            _expansions_in_tree_have_extent=True,
            target_association_tolerance=0.05,
            )

    fplot = FieldPlotter(np.zeros(2), extent=5, npoints=500)
    from pytential.target import PointsTarget
    ptarget = PointsTarget(fplot.points)
    from sumpy.kernel import LaplaceKernel

    places = GeometryCollection({
        "direct_qbx": direct_qbx,
        "fmm_qbx": fmm_qbx,
        "target": ptarget})

    direct_density_discr = places.get_discretization("direct_qbx")
    fmm_density_discr = places.get_discretization("fmm_qbx")

    from pytential.qbx import QBXTargetAssociationFailedException
    op = sym.D(LaplaceKernel(2), sym.var("sigma"), qbx_forced_limit=None)
    try:
        direct_sigma = direct_density_discr.zeros(actx) + 1
        direct_fld_in_vol = bind(places, op,
                auto_where=("direct_qbx", "target"))(
                        actx, sigma=direct_sigma)
    except QBXTargetAssociationFailedException as e:
        fplot.show_scalar_in_matplotlib(
            actx.to_numpy(actx.thaw(e.failed_target_flags)))
        import matplotlib.pyplot as pt
        pt.show()
        raise

    fmm_sigma = fmm_density_discr.zeros(actx) + 1
    fmm_fld_in_vol = bind(places, op,
            auto_where=("fmm_qbx", "target"))(
                    actx, sigma=fmm_sigma)

    err = actx.np.fabs(fmm_fld_in_vol - direct_fld_in_vol)
    linf_err = actx.to_numpy(err).max()
    print("l_inf error:", linf_err)

    if do_plot:
        #fplot.show_scalar_in_mayavi(0.1*.get(queue))
        fplot.write_vtk_file("potential.vts", [
            ("fmm_fld_in_vol", actx.to_numpy(fmm_fld_in_vol)),
            ("direct_fld_in_vol", actx.to_numpy(direct_fld_in_vol))
            ])

    assert linf_err < 1e-3
Esempio n. 25
0
def main(nelements):
    import logging
    logging.basicConfig(level=logging.INFO)

    def get_obj_array(obj_array):
        from pytools.obj_array import make_obj_array
        return make_obj_array([ary.get() for ary in obj_array])

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

    from meshmode.mesh.generation import (  # noqa
        make_curve_mesh, starfish, ellipse, drop)
    mesh = make_curve_mesh(lambda t: starfish(t),
                           np.linspace(0, 1, nelements + 1), target_order)
    coarse_density_discr = Discretization(
        cl_ctx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))

    from pytential.qbx import QBXLayerPotentialSource
    target_association_tolerance = 0.05
    qbx, _ = QBXLayerPotentialSource(
        coarse_density_discr,
        fine_order=ovsmp_target_order,
        qbx_order=qbx_order,
        fmm_order=fmm_order,
        target_association_tolerance=target_association_tolerance,
    ).with_refinement()

    density_discr = qbx.density_discr
    nodes = density_discr.nodes().with_queue(queue)

    # Get normal vectors for the density discretization -- used in integration with stresslet
    mv_normal = bind(density_discr, sym.normal(2))(queue)
    normal = mv_normal.as_vector(np.object)

    # {{{ describe bvp

    from sumpy.kernel import LaplaceKernel
    from pytential.symbolic.stokes import StressletWrapper
    from pytools.obj_array import make_obj_array
    dim = 2
    cse = sym.cse

    nvec_sym = sym.make_sym_vector("normal", dim)
    sigma_sym = sym.make_sym_vector("sigma", dim)
    mu_sym = sym.var("mu")
    sqrt_w = sym.sqrt_jac_q_weight(2)
    inv_sqrt_w_sigma = cse(sigma_sym / sqrt_w)

    # -1 for interior Dirichlet
    # +1 for exterior Dirichlet
    loc_sign = -1

    # Create stresslet object
    stresslet_obj = StressletWrapper(dim=2)

    # Describe boundary operator
    bdry_op_sym = loc_sign * 0.5 * sigma_sym + sqrt_w * stresslet_obj.apply(
        inv_sqrt_w_sigma, nvec_sym, mu_sym, qbx_forced_limit='avg')

    # Bind to the qbx discretization
    bound_op = bind(qbx, bdry_op_sym)

    # }}}

    # {{{ fix rhs and solve

    def fund_soln(x, y, loc):
        #with direction (1,0) for point source
        r = cl.clmath.sqrt((x - loc[0])**2 + (y - loc[1])**2)
        scaling = 1. / (4 * np.pi * mu)
        xcomp = (-cl.clmath.log(r) + (x - loc[0])**2 / r**2) * scaling
        ycomp = ((x - loc[0]) * (y - loc[1]) / r**2) * scaling
        return [xcomp, ycomp]

    def couette_soln(x, y, dp, h):
        scaling = 1. / (2 * mu)
        xcomp = scaling * dp * ((y + (h / 2.))**2 - h * (y + (h / 2.)))
        ycomp = scaling * 0 * y
        return [xcomp, ycomp]

    if soln_type == 'fundamental':
        pt_loc = np.array([2.0, 0.0])
        bc = fund_soln(nodes[0], nodes[1], pt_loc)
    else:
        dp = -10.
        h = 2.5
        bc = couette_soln(nodes[0], nodes[1], dp, h)

    # Get rhs vector
    bvp_rhs = bind(qbx, sqrt_w * sym.make_sym_vector("bc", dim))(queue, bc=bc)

    from pytential.solve import gmres
    gmres_result = gmres(bound_op.scipy_op(queue,
                                           "sigma",
                                           np.float64,
                                           mu=mu,
                                           normal=normal),
                         bvp_rhs,
                         tol=1e-9,
                         progress=True,
                         stall_iterations=0,
                         hard_failure=True)

    # }}}

    # {{{ postprocess/visualize
    sigma = gmres_result.solution

    # Describe representation of solution for evaluation in domain
    representation_sym = stresslet_obj.apply(inv_sqrt_w_sigma,
                                             nvec_sym,
                                             mu_sym,
                                             qbx_forced_limit=-2)

    from sumpy.visualization import FieldPlotter
    nsamp = 10
    eval_points_1d = np.linspace(-1., 1., nsamp)
    eval_points = np.zeros((2, len(eval_points_1d)**2))
    eval_points[0, :] = np.tile(eval_points_1d, len(eval_points_1d))
    eval_points[1, :] = np.repeat(eval_points_1d, len(eval_points_1d))

    gamma_sym = sym.var("gamma")
    inv_sqrt_w_gamma = cse(gamma_sym / sqrt_w)
    constant_laplace_rep = sym.D(LaplaceKernel(dim=2),
                                 inv_sqrt_w_gamma,
                                 qbx_forced_limit=None)
    sqrt_w_vec = bind(qbx, sqrt_w)(queue)

    def general_mask(test_points):
        const_density = bind((qbx, PointsTarget(test_points)),
                             constant_laplace_rep)(queue,
                                                   gamma=sqrt_w_vec).get()
        return (abs(const_density) > 0.1)

    def inside_domain(test_points):
        mask = general_mask(test_points)
        return np.array([row[mask] for row in test_points])

    def stride_hack(arr):
        from numpy.lib.stride_tricks import as_strided
        return np.array(as_strided(arr, strides=(8 * len(arr[0]), 8)))

    eval_points = inside_domain(eval_points)
    eval_points_dev = cl.array.to_device(queue, eval_points)

    # Evaluate the solution at the evaluation points
    vel = bind((qbx, PointsTarget(eval_points_dev)),
               representation_sym)(queue, sigma=sigma, mu=mu, normal=normal)
    print("@@@@@@@@")
    vel = get_obj_array(vel)

    if soln_type == 'fundamental':
        exact_soln = fund_soln(eval_points_dev[0], eval_points_dev[1], pt_loc)
    else:
        exact_soln = couette_soln(eval_points_dev[0], eval_points_dev[1], dp,
                                  h)
    err = vel - get_obj_array(exact_soln)

    print("@@@@@@@@")

    print(
        "L2 error estimate: ",
        np.sqrt((2. / (nsamp - 1))**2 * np.sum(err[0] * err[0]) +
                (2. / (nsamp - 1))**2 * np.sum(err[1] * err[1])))
    max_error_loc = [abs(err[0]).argmax(), abs(err[1]).argmax()]
    print("max error at sampled points: ", max(abs(err[0])), max(abs(err[1])))
    print("exact velocity at max error points: x -> ",
          err[0][max_error_loc[0]], ", y -> ", err[1][max_error_loc[1]])

    from pytential.symbolic.mappers import DerivativeTaker
    rep_pressure = stresslet_obj.apply_pressure(inv_sqrt_w_sigma,
                                                nvec_sym,
                                                mu_sym,
                                                qbx_forced_limit=-2)
    pressure = bind((qbx, PointsTarget(eval_points_dev)),
                    rep_pressure)(queue, sigma=sigma, mu=mu, normal=normal)
    pressure = pressure.get()
    print "pressure = ", pressure

    x_dir_vecs = np.zeros((2, len(eval_points[0])))
    x_dir_vecs[0, :] = 1.0
    y_dir_vecs = np.zeros((2, len(eval_points[0])))
    y_dir_vecs[1, :] = 1.0
    x_dir_vecs = cl.array.to_device(queue, x_dir_vecs)
    y_dir_vecs = cl.array.to_device(queue, y_dir_vecs)
    dir_vec_sym = sym.make_sym_vector("force_direction", dim)
    rep_stress = stresslet_obj.apply_stress(inv_sqrt_w_sigma,
                                            nvec_sym,
                                            dir_vec_sym,
                                            mu_sym,
                                            qbx_forced_limit=-2)

    applied_stress_x = bind((qbx, PointsTarget(eval_points_dev)),
                            rep_stress)(queue,
                                        sigma=sigma,
                                        normal=normal,
                                        force_direction=x_dir_vecs,
                                        mu=mu)
    applied_stress_x = get_obj_array(applied_stress_x)
    applied_stress_y = bind((qbx, PointsTarget(eval_points_dev)),
                            rep_stress)(queue,
                                        sigma=sigma,
                                        normal=normal,
                                        force_direction=y_dir_vecs,
                                        mu=mu)
    applied_stress_y = get_obj_array(applied_stress_y)

    print "stress applied to x direction: ", applied_stress_x
    print "stress applied to y direction: ", applied_stress_y

    import matplotlib.pyplot as plt
    plt.quiver(eval_points[0], eval_points[1], vel[0], vel[1], linewidth=0.1)
    file_name = "field-n%s.pdf" % (nelements)
    plt.savefig(file_name)

    return (max(abs(err[0])), max(abs(err[1])))
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
def test_sphere_eigenvalues(ctx_factory, mode_m, mode_n, qbx_order,
                            fmm_backend):
    logging.basicConfig(level=logging.INFO)

    special = pytest.importorskip("scipy.special")

    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()
    dp_eoc_rec = EOCRecorder()

    def rel_err(comp, ref):
        return (norm(density_discr, comp - ref) / norm(density_discr, ref))

    for nrefinements in [0, 1]:
        from meshmode.mesh.generation import generate_icosphere
        mesh = generate_icosphere(1, target_order)
        from meshmode.mesh.refinement import Refiner

        refiner = Refiner(mesh)
        for i in range(nrefinements):
            flags = np.ones(mesh.nelements, dtype=bool)
            refiner.refine(flags)
            mesh = refiner.get_current_mesh()

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

        from meshmode.dof_array import flatten, unflatten, thaw

        density_discr = places.get_discretization(places.auto_source.geometry)
        nodes = thaw(actx, density_discr.nodes())
        r = actx.np.sqrt(nodes[0] * nodes[0] + nodes[1] * nodes[1] +
                         nodes[2] * nodes[2])
        phi = actx.np.arccos(nodes[2] / r)
        theta = actx.np.arctan2(nodes[0], nodes[1])

        ymn = unflatten(
            actx, density_discr,
            actx.from_numpy(
                special.sph_harm(mode_m, mode_n, actx.to_numpy(flatten(theta)),
                                 actx.to_numpy(flatten(phi)))))

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

        # {{{ single layer

        s_sigma_op = bind(
            places, sym.S(lap_knl, sym.var("sigma"), qbx_forced_limit=+1))
        s_sigma = s_sigma_op(actx, sigma=ymn)
        s_eigval = 1 / (2 * mode_n + 1)

        h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx)
        s_eoc_rec.add_data_point(h_max, rel_err(s_sigma, s_eigval * ymn))

        # }}}

        # {{{ double layer

        d_sigma_op = bind(
            places, sym.D(lap_knl, sym.var("sigma"), qbx_forced_limit="avg"))
        d_sigma = d_sigma_op(actx, sigma=ymn)
        d_eigval = -1 / (2 * (2 * mode_n + 1))
        d_eoc_rec.add_data_point(h_max, rel_err(d_sigma, d_eigval * ymn))

        # }}}

        # {{{ S'

        sp_sigma_op = bind(
            places, sym.Sp(lap_knl, sym.var("sigma"), qbx_forced_limit="avg"))
        sp_sigma = sp_sigma_op(actx, sigma=ymn)
        sp_eigval = -1 / (2 * (2 * mode_n + 1))

        sp_eoc_rec.add_data_point(h_max, rel_err(sp_sigma, sp_eigval * ymn))

        # }}}

        # {{{ D'

        dp_sigma_op = bind(
            places, sym.Dp(lap_knl, sym.var("sigma"), qbx_forced_limit="avg"))
        dp_sigma = dp_sigma_op(actx, sigma=ymn)
        dp_eigval = -(mode_n * (mode_n + 1)) / (2 * mode_n + 1)

        dp_eoc_rec.add_data_point(h_max, rel_err(dp_sigma, dp_eigval * ymn))

        # }}}

    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 - 0.5

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

    print("Errors for D':")
    print(dp_eoc_rec)
    required_order = qbx_order
    assert dp_eoc_rec.order_estimate() > required_order - 1.5
Esempio n. 28
0
def main(mesh_name="ellipsoid"):
    import logging
    logger = logging.getLogger(__name__)
    logging.basicConfig(level=logging.WARNING)  # INFO for more progress info

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

    if mesh_name == "ellipsoid":
        cad_file_name = "geometries/ellipsoid.step"
        h = 0.6
    elif mesh_name == "two-cylinders":
        cad_file_name = "geometries/two-cylinders-smooth.step"
        h = 0.4
    else:
        raise ValueError("unknown mesh name: %s" % mesh_name)

    from meshmode.mesh.io import generate_gmsh, FileSource
    mesh = generate_gmsh(
        FileSource(cad_file_name),
        2,
        order=2,
        other_options=["-string",
                       "Mesh.CharacteristicLengthMax = %g;" % h],
        target_unit="MM")

    from meshmode.mesh.processing import perform_flips
    # Flip elements--gmsh generates inside-out geometry.
    mesh = perform_flips(mesh, np.ones(mesh.nelements))

    from meshmode.mesh.processing import find_bounding_box
    bbox_min, bbox_max = find_bounding_box(mesh)
    bbox_center = 0.5 * (bbox_min + bbox_max)
    bbox_size = max(bbox_max - bbox_min) / 2

    logger.info("%d elements" % mesh.nelements)

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

    density_discr = Discretization(
        actx, mesh, InterpolatoryQuadratureSimplexGroupFactory(target_order))

    qbx = QBXLayerPotentialSource(density_discr,
                                  4 * target_order,
                                  qbx_order,
                                  fmm_order=qbx_order + 3,
                                  target_association_tolerance=0.15)

    from pytential.target import PointsTarget
    fplot = FieldPlotter(bbox_center, extent=3.5 * bbox_size, npoints=150)

    from pytential import GeometryCollection
    places = GeometryCollection(
        {
            "qbx": qbx,
            "targets": PointsTarget(fplot.points)
        }, auto_where="qbx")
    density_discr = places.get_discretization("qbx")

    nodes = thaw(actx, density_discr.nodes())
    angle = actx.np.arctan2(nodes[1], nodes[0])

    if k:
        kernel = HelmholtzKernel(3)
    else:
        kernel = LaplaceKernel(3)

    #op = sym.d_dx(sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None))
    op = sym.D(kernel, sym.var("sigma"), qbx_forced_limit=None)
    #op = sym.S(kernel, sym.var("sigma"), qbx_forced_limit=None)

    sigma = actx.np.cos(mode_nr * angle)
    if 0:
        from meshmode.dof_array import flatten, unflatten
        sigma = flatten(0 * angle)
        from random import randrange
        for i in range(5):
            sigma[randrange(len(sigma))] = 1
        sigma = unflatten(actx, density_discr, sigma)

    if isinstance(kernel, HelmholtzKernel):
        for i, elem in np.ndenumerate(sigma):
            sigma[i] = elem.astype(np.complex128)

    fld_in_vol = actx.to_numpy(
        bind(places, op, auto_where=("qbx", "targets"))(actx, sigma=sigma,
                                                        k=k))

    #fplot.show_scalar_in_mayavi(fld_in_vol.real, max_val=5)
    fplot.write_vtk_file("layerpot-3d-potential.vts",
                         [("potential", fld_in_vol)])

    bdry_normals = bind(places, sym.normal(
        density_discr.ambient_dim))(actx).as_vector(dtype=object)

    from meshmode.discretization.visualization import make_visualizer
    bdry_vis = make_visualizer(actx, density_discr, target_order)
    bdry_vis.write_vtk_file("layerpot-3d-density.vtu", [
        ("sigma", sigma),
        ("bdry_normals", bdry_normals),
    ])
Esempio n. 29
0
 def D(self, dom_idx, density, qbx_forced_limit="avg"):  # noqa
     return sym.D(self.kernel,
                  density,
                  k=self.domain_K_exprs[dom_idx],
                  qbx_forced_limit=qbx_forced_limit)
Esempio n. 30
0
def test_3d_jump_relations(ctx_factory, relation, visualize=False):
    # logging.basicConfig(level=logging.INFO)

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

    if relation == "div_s":
        target_order = 3
    else:
        target_order = 4

    qbx_order = target_order

    from pytools.convergence import EOCRecorder
    eoc_rec = EOCRecorder()

    for nel_factor in [6, 10, 14]:
        from meshmode.mesh.generation import generate_torus
        mesh = generate_torus(
                5, 2, order=target_order,
                n_major=2*nel_factor, n_minor=nel_factor)

        from meshmode.discretization import Discretization
        from meshmode.discretization.poly_element import \
            InterpolatoryQuadratureSimplexGroupFactory
        pre_discr = Discretization(
                actx, mesh,
                InterpolatoryQuadratureSimplexGroupFactory(3))

        from pytential.qbx import QBXLayerPotentialSource
        qbx = QBXLayerPotentialSource(
                pre_discr, fine_order=4*target_order,
                qbx_order=qbx_order,
                fmm_order=qbx_order + 5,
                fmm_backend="fmmlib"
                )

        places = GeometryCollection(qbx)
        density_discr = places.get_discretization(places.auto_source.geometry)

        from sumpy.kernel import LaplaceKernel
        knl = LaplaceKernel(3)

        def nxcurlS(qbx_forced_limit):

            return sym.n_cross(sym.curl(sym.S(
                knl,
                sym.cse(sym.tangential_to_xyz(density_sym), "jxyz"),
                qbx_forced_limit=qbx_forced_limit)))

        from meshmode.dof_array import thaw
        x, y, z = thaw(actx, density_discr.nodes())
        m = actx.np

        if relation == "nxcurls":
            density_sym = sym.make_sym_vector("density", 2)

            jump_identity_sym = (
                    nxcurlS(+1)
                    - (nxcurlS("avg") + 0.5*sym.tangential_to_xyz(density_sym)))

            # The tangential coordinate system is element-local, so we can't just
            # conjure up some globally smooth functions, interpret their values
            # in the tangential coordinate system, and be done. Instead, generate
            # an XYZ function and project it.
            density = bind(places,
                    sym.xyz_to_tangential(sym.make_sym_vector("jxyz", 3)))(
                            actx,
                            jxyz=sym.make_obj_array([
                                m.cos(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z),
                                m.sin(0.5*x) * m.cos(0.5*y) * m.sin(0.5*z),
                                m.sin(0.5*x) * m.cos(0.5*y) * m.cos(0.5*z),
                                ]))

        elif relation == "sp":

            density = m.cos(2*x) * m.cos(2*y) * m.cos(z)
            density_sym = sym.var("density")

            jump_identity_sym = (
                    sym.Sp(knl, density_sym, qbx_forced_limit=+1)
                    - (sym.Sp(knl, density_sym, qbx_forced_limit="avg")
                        - 0.5*density_sym))

        elif relation == "div_s":

            density = m.cos(2*x) * m.cos(2*y) * m.cos(z)
            density_sym = sym.var("density")

            jump_identity_sym = (
                    sym.div(sym.S(knl, sym.normal(3).as_vector()*density_sym,
                        qbx_forced_limit="avg"))
                    + sym.D(knl, density_sym, qbx_forced_limit="avg"))

        else:
            raise ValueError("unexpected value of 'relation': %s" % relation)

        bound_jump_identity = bind(places, jump_identity_sym)
        jump_identity = bound_jump_identity(actx, density=density)

        h_max = bind(places, sym.h_max(qbx.ambient_dim))(actx)
        err = (
                norm(density_discr, jump_identity, np.inf)
                / norm(density_discr, density, np.inf))
        print("ERROR", h_max, err)

        eoc_rec.add_data_point(h_max, err)

        # {{{ visualization

        if visualize and relation == "nxcurls":
            nxcurlS_ext = bind(places, nxcurlS(+1))(actx, density=density)
            nxcurlS_avg = bind(places, nxcurlS("avg"))(actx, density=density)
            jtxyz = bind(places, sym.tangential_to_xyz(density_sym))(
                    actx, density=density)

            from meshmode.discretization.visualization import make_visualizer
            bdry_vis = make_visualizer(actx, qbx.density_discr, target_order+3)

            bdry_normals = bind(places, sym.normal(3))(actx)\
                    .as_vector(dtype=object)

            bdry_vis.write_vtk_file("source-%s.vtu" % nel_factor, [
                ("jt", jtxyz),
                ("nxcurlS_ext", nxcurlS_ext),
                ("nxcurlS_avg", nxcurlS_avg),
                ("bdry_normals", bdry_normals),
                ])

        if visualize and relation == "sp":
            op = sym.Sp(knl, density_sym, qbx_forced_limit=+1)
            sp_ext = bind(places, op)(actx, density=density)
            op = sym.Sp(knl, density_sym, qbx_forced_limit="avg")
            sp_avg = bind(places, op)(actx, density=density)

            from meshmode.discretization.visualization import make_visualizer
            bdry_vis = make_visualizer(actx, qbx.density_discr, target_order+3)

            bdry_normals = bind(places,
                    sym.normal(3))(actx).as_vector(dtype=object)

            bdry_vis.write_vtk_file("source-%s.vtu" % nel_factor, [
                ("density", density),
                ("sp_ext", sp_ext),
                ("sp_avg", sp_avg),
                ("bdry_normals", bdry_normals),
                ])

        # }}}

    print(eoc_rec)

    assert eoc_rec.order_estimate() >= qbx_order - 1.5