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()
示例#2
0
def run_int_eq_test(cl_ctx, queue, case, resolution, visualize):
    mesh = case.get_mesh(resolution, case.target_order)
    print("%d elements" % mesh.nelements)

    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(case.target_order))

    source_order = 4*case.target_order

    refiner_extra_kwargs = {}

    qbx_lpot_kwargs = {}
    if case.fmm_backend is None:
        qbx_lpot_kwargs["fmm_order"] = False
    else:
        if hasattr(case, "fmm_tol"):
            from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder
            qbx_lpot_kwargs["fmm_level_to_order"] = SimpleExpansionOrderFinder(
                    case.fmm_tol)

        elif hasattr(case, "fmm_order"):
            qbx_lpot_kwargs["fmm_order"] = case.fmm_order
        else:
            qbx_lpot_kwargs["fmm_order"] = case.qbx_order + 5

    qbx = QBXLayerPotentialSource(
            pre_density_discr,
            fine_order=source_order,
            qbx_order=case.qbx_order,

            _box_extent_norm=getattr(case, "box_extent_norm", None),
            _from_sep_smaller_crit=getattr(case, "from_sep_smaller_crit", None),
            _from_sep_smaller_min_nsources_cumul=30,
            fmm_backend=case.fmm_backend, **qbx_lpot_kwargs)

    if case.use_refinement:
        if case.k != 0 and getattr(case, "refine_on_helmholtz_k", True):
            refiner_extra_kwargs["kernel_length_scale"] = 5/case.k

        if hasattr(case, "scaled_max_curvature_threshold"):
            refiner_extra_kwargs["_scaled_max_curvature_threshold"] = \
                    case.scaled_max_curvature_threshold

        if hasattr(case, "expansion_disturbance_tolerance"):
            refiner_extra_kwargs["_expansion_disturbance_tolerance"] = \
                    case.expansion_disturbance_tolerance

        if hasattr(case, "refinement_maxiter"):
            refiner_extra_kwargs["maxiter"] = case.refinement_maxiter

        #refiner_extra_kwargs["visualize"] = True

        print("%d elements before refinement" % pre_density_discr.mesh.nelements)
        qbx, _ = qbx.with_refinement(**refiner_extra_kwargs)
        print("%d stage-1 elements after refinement"
                % qbx.density_discr.mesh.nelements)
        print("%d stage-2 elements after refinement"
                % qbx.stage2_density_discr.mesh.nelements)
        print("quad stage-2 elements have %d nodes"
                % qbx.quad_stage2_density_discr.groups[0].nunit_nodes)

    density_discr = qbx.density_discr

    if hasattr(case, "visualize_geometry") and case.visualize_geometry:
        bdry_normals = bind(
                density_discr, sym.normal(mesh.ambient_dim)
                )(queue).as_vector(dtype=object)

        bdry_vis = make_visualizer(queue, density_discr, case.target_order)
        bdry_vis.write_vtk_file("geometry.vtu", [
            ("normals", bdry_normals)
            ])

    # {{{ plot geometry

    if 0:
        if mesh.ambient_dim == 2:
            # show geometry, centers, normals
            nodes_h = density_discr.nodes().get(queue=queue)
            pt.plot(nodes_h[0], nodes_h[1], "x-")
            normal = bind(density_discr, sym.normal(2))(queue).as_vector(np.object)
            pt.quiver(nodes_h[0], nodes_h[1],
                    normal[0].get(queue), normal[1].get(queue))
            pt.gca().set_aspect("equal")
            pt.show()

        elif mesh.ambient_dim == 3:
            bdry_vis = make_visualizer(queue, density_discr, case.target_order+3)

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

            bdry_vis.write_vtk_file("pre-solve-source-%s.vtu" % resolution, [
                ("bdry_normals", bdry_normals),
                ])

        else:
            raise ValueError("invalid mesh dim")

    # }}}

    # {{{ set up operator

    from pytential.symbolic.pde.scalar import (
            DirichletOperator,
            NeumannOperator)

    from sumpy.kernel import LaplaceKernel, HelmholtzKernel
    if case.k:
        knl = HelmholtzKernel(mesh.ambient_dim)
        knl_kwargs = {"k": sym.var("k")}
        concrete_knl_kwargs = {"k": case.k}
    else:
        knl = LaplaceKernel(mesh.ambient_dim)
        knl_kwargs = {}
        concrete_knl_kwargs = {}

    if knl.is_complex_valued:
        dtype = np.complex128
    else:
        dtype = np.float64

    loc_sign = +1 if case.prob_side in [+1, "scat"] else -1

    if case.bc_type == "dirichlet":
        op = DirichletOperator(knl, loc_sign, use_l2_weighting=True,
                kernel_arguments=knl_kwargs)
    elif case.bc_type == "neumann":
        op = NeumannOperator(knl, loc_sign, use_l2_weighting=True,
                 use_improved_operator=False, kernel_arguments=knl_kwargs)
    else:
        assert False

    op_u = op.operator(sym.var("u"))

    # }}}

    # {{{ set up test data

    if case.prob_side == -1:
        test_src_geo_radius = case.outer_radius
        test_tgt_geo_radius = case.inner_radius
    elif case.prob_side == +1:
        test_src_geo_radius = case.inner_radius
        test_tgt_geo_radius = case.outer_radius
    elif case.prob_side == "scat":
        test_src_geo_radius = case.outer_radius
        test_tgt_geo_radius = case.outer_radius
    else:
        raise ValueError("unknown problem_side")

    point_sources = make_circular_point_group(
            mesh.ambient_dim, 10, test_src_geo_radius,
            func=lambda x: x**1.5)
    test_targets = make_circular_point_group(
            mesh.ambient_dim, 20, test_tgt_geo_radius)

    np.random.seed(22)
    source_charges = np.random.randn(point_sources.shape[1])
    source_charges[-1] = -np.sum(source_charges[:-1])
    source_charges = source_charges.astype(dtype)
    assert np.sum(source_charges) < 1e-15

    source_charges_dev = cl.array.to_device(queue, source_charges)

    # }}}

    # {{{ establish BCs

    from pytential.source import PointPotentialSource
    from pytential.target import PointsTarget

    point_source = PointPotentialSource(cl_ctx, point_sources)

    pot_src = sym.IntG(
        # FIXME: qbx_forced_limit--really?
        knl, sym.var("charges"), qbx_forced_limit=None, **knl_kwargs)

    test_direct = bind((point_source, PointsTarget(test_targets)), pot_src)(
            queue, charges=source_charges_dev, **concrete_knl_kwargs)

    if case.bc_type == "dirichlet":
        bc = bind((point_source, density_discr), pot_src)(
                queue, charges=source_charges_dev, **concrete_knl_kwargs)

    elif case.bc_type == "neumann":
        bc = bind(
                (point_source, density_discr),
                sym.normal_derivative(
                    qbx.ambient_dim, pot_src, where=sym.DEFAULT_TARGET)
                )(queue, charges=source_charges_dev, **concrete_knl_kwargs)

    # }}}

    # {{{ solve

    bound_op = bind(qbx, op_u)

    rhs = bind(density_discr, op.prepare_rhs(sym.var("bc")))(queue, bc=bc)

    try:
        from pytential.solve import gmres
        gmres_result = gmres(
                bound_op.scipy_op(queue, "u", dtype, **concrete_knl_kwargs),
                rhs,
                tol=case.gmres_tol,
                progress=True,
                hard_failure=True,
                stall_iterations=50, no_progress_factor=1.05)
    except QBXTargetAssociationFailedException as e:
        bdry_vis = make_visualizer(queue, density_discr, case.target_order+3)

        bdry_vis.write_vtk_file("failed-targets-%s.vtu" % resolution, [
            ("failed_targets", e.failed_target_flags),
            ])
        raise

    print("gmres state:", gmres_result.state)
    weighted_u = gmres_result.solution

    # }}}

    # {{{ build matrix for spectrum check

    if 0:
        from sumpy.tools import build_matrix
        mat = build_matrix(
                bound_op.scipy_op(
                    queue, arg_name="u", dtype=dtype, k=case.k))
        w, v = la.eig(mat)
        if 0:
            pt.imshow(np.log10(1e-20+np.abs(mat)))
            pt.colorbar()
            pt.show()

        #assert abs(s[-1]) < 1e-13, "h
        #assert abs(s[-2]) > 1e-7
        #from pudb import set_trace; set_trace()

    # }}}

    if case.prob_side != "scat":
        # {{{ error check

        points_target = PointsTarget(test_targets)
        bound_tgt_op = bind((qbx, points_target),
                op.representation(sym.var("u")))

        test_via_bdry = bound_tgt_op(queue, u=weighted_u, k=case.k)

        err = test_via_bdry - test_direct

        err = err.get()
        test_direct = test_direct.get()
        test_via_bdry = test_via_bdry.get()

        # {{{ remove effect of net source charge

        if case.k == 0 and case.bc_type == "neumann" and loc_sign == -1:

            # remove constant offset in interior Laplace Neumann error
            tgt_ones = np.ones_like(test_direct)
            tgt_ones = tgt_ones/la.norm(tgt_ones)
            err = err - np.vdot(tgt_ones, err)*tgt_ones

        # }}}

        rel_err_2 = la.norm(err)/la.norm(test_direct)
        rel_err_inf = la.norm(err, np.inf)/la.norm(test_direct, np.inf)

        # }}}

        print("rel_err_2: %g rel_err_inf: %g" % (rel_err_2, rel_err_inf))

    else:
        rel_err_2 = None
        rel_err_inf = None

    # {{{ test gradient

    if case.check_gradient and case.prob_side != "scat":
        bound_grad_op = bind((qbx, points_target),
                op.representation(
                    sym.var("u"),
                    map_potentials=lambda pot: sym.grad(mesh.ambient_dim, pot),
                    qbx_forced_limit=None))

        #print(bound_t_deriv_op.code)

        grad_from_src = bound_grad_op(
                queue, u=weighted_u, **concrete_knl_kwargs)

        grad_ref = (bind(
                (point_source, points_target),
                sym.grad(mesh.ambient_dim, pot_src)
                )(queue, charges=source_charges_dev, **concrete_knl_kwargs)
                )

        grad_err = (grad_from_src - grad_ref)

        rel_grad_err_inf = (
                la.norm(grad_err[0].get(), np.inf)
                / la.norm(grad_ref[0].get(), np.inf))

        print("rel_grad_err_inf: %g" % rel_grad_err_inf)

    # }}}

    # {{{ test tangential derivative

    if case.check_tangential_deriv and case.prob_side != "scat":
        bound_t_deriv_op = bind(qbx,
                op.representation(
                    sym.var("u"),
                    map_potentials=lambda pot: sym.tangential_derivative(2, pot),
                    qbx_forced_limit=loc_sign))

        #print(bound_t_deriv_op.code)

        tang_deriv_from_src = bound_t_deriv_op(
                queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get()

        tang_deriv_ref = (bind(
                (point_source, density_discr),
                sym.tangential_derivative(2, pot_src)
                )(queue, charges=source_charges_dev, **concrete_knl_kwargs)
                .as_scalar().get())

        if 0:
            pt.plot(tang_deriv_ref.real)
            pt.plot(tang_deriv_from_src.real)
            pt.show()

        td_err = (tang_deriv_from_src - tang_deriv_ref)

        rel_td_err_inf = la.norm(td_err, np.inf)/la.norm(tang_deriv_ref, np.inf)

        print("rel_td_err_inf: %g" % rel_td_err_inf)

    else:
        rel_td_err_inf = None

    # }}}

    # {{{ any-D file plotting

    if visualize:
        bdry_vis = make_visualizer(queue, density_discr, case.target_order+3)

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

        sym_sqrt_j = sym.sqrt_jac_q_weight(density_discr.ambient_dim)
        u = bind(density_discr, sym.var("u")/sym_sqrt_j)(queue, u=weighted_u)

        bdry_vis.write_vtk_file("source-%s.vtu" % resolution, [
            ("u", u),
            ("bc", bc),
            #("bdry_normals", bdry_normals),
            ])

        from sumpy.visualization import make_field_plotter_from_bbox  # noqa
        from meshmode.mesh.processing import find_bounding_box

        vis_grid_spacing = (0.1, 0.1, 0.1)[:qbx.ambient_dim]
        if hasattr(case, "vis_grid_spacing"):
            vis_grid_spacing = case.vis_grid_spacing
        vis_extend_factor = 0.2
        if hasattr(case, "vis_extend_factor"):
            vis_grid_spacing = case.vis_grid_spacing

        fplot = make_field_plotter_from_bbox(
                find_bounding_box(mesh),
                h=vis_grid_spacing,
                extend_factor=vis_extend_factor)

        qbx_tgt_tol = qbx.copy(target_association_tolerance=0.15)
        from pytential.target import PointsTarget

        try:
            solved_pot = bind(
                    (qbx_tgt_tol, PointsTarget(fplot.points)),
                    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),
                        ]
                    )
        else:
            fplot.write_vtk_file(
                    "potential-%s.vts" % resolution,
                    [
                        ("solved_pot", solved_pot),
                        ("true_pot", true_pot),
                        ("indicator", indicator),
                        ]
                    )

    # }}}

    class Result(Record):
        pass

    return Result(
            h_max=qbx.h_max,
            rel_err_2=rel_err_2,
            rel_err_inf=rel_err_inf,
            rel_td_err_inf=rel_td_err_inf,
            gmres_result=gmres_result)
示例#3
0
        rel_grad_err_inf = (la.norm(grad_err[0].get(), np.inf) /
                            la.norm(grad_ref[0].get(), np.inf))

        print("rel_grad_err_inf: %g" % rel_grad_err_inf)

    # }}}

    # {{{ test tangential derivative

    if case.check_tangential_deriv and case.prob_side != "scat":
        bound_t_deriv_op = bind(
            qbx,
            op.representation(
                sym.var("u"),
                map_potentials=lambda pot: sym.tangential_derivative(2, pot),
                qbx_forced_limit=loc_sign))

        #print(bound_t_deriv_op.code)

        tang_deriv_from_src = bound_t_deriv_op(
            queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get()

        tang_deriv_ref = (bind((point_source, density_discr),
                               sym.tangential_derivative(2, pot_src))(
                                   queue,
                                   charges=source_charges_dev,
                                   **concrete_knl_kwargs).as_scalar().get())

        if 0:
            pt.plot(tang_deriv_ref.real)
示例#4
0
    def bc_term_to_operator_contrib(self, term, side, raw_potential_op,
                                    density, discrete):
        potential_op = raw_potential_op

        side_sign = self.side_to_sign[side]

        domain_outer, domain_inner, interface_id = \
                self.interfaces[term.i_interface]
        if side == self.side_in:
            K_expr = self.domain_K_exprs[domain_inner]  # noqa
            bc_coeff = term.coeff_inner
        elif side == self.side_out:
            K_expr = self.domain_K_exprs[domain_outer]  # noqa
            bc_coeff = term.coeff_outer
        else:
            raise ValueError("invalid value of 'side'")

        potential_op = potential_op(self.kernel,
                                    density,
                                    source=interface_id,
                                    k=K_expr)

        if term.direction == self.dir_none:
            if raw_potential_op is sym.S:
                jump_term = 0
            elif raw_potential_op is sym.D:
                jump_term = (side_sign * 0.5) * discrete
            else:
                assert False, raw_potential_op
        elif term.direction == self.dir_normal:
            potential_op = sym.normal_derivative(potential_op, interface_id)

            if raw_potential_op is sym.S:
                # S'
                jump_term = (-side_sign * 0.5) * discrete
            elif raw_potential_op is sym.D:
                jump_term = 0
            else:
                assert False, raw_potential_op

        elif term.direction == self.dir_tangential:
            potential_op = sym.tangential_derivative(
                raw_potential_op(self.kernel,
                                 density,
                                 source=interface_id,
                                 k=K_expr,
                                 qbx_forced_limit=side_sign),
                interface_id).a.as_scalar()

            # Some of these may have jumps, but QBX does the dirty
            # work here by directly computing the limit.
            jump_term = 0

        else:
            raise ValueError("invalid direction")

        potential_op = (jump_term +
                        self.get_sqrt_weight(interface_id) * potential_op)

        del jump_term

        contrib = bc_coeff * potential_op

        if (raw_potential_op is sym.D and term.direction == self.dir_normal):
            # FIXME The hypersingular part should perhaps be
            # treated specially to avoid cancellation.
            pass

        return contrib
示例#5
0
 def St(dom, density):  # noqa
     return sym.tangential_derivative(2, S(dom,
                                           density)).xproject(0)
示例#6
0
 def Tt(where, dom, density):  # noqa
     return sym.tangential_derivative(2, T(where, dom,
                                           density)).xproject(0)
        grad_err = actx.to_numpy(flatten(grad_err[0], actx))

        rel_grad_err_inf = la.norm(grad_err, np.inf) / la.norm(
            grad_ref, np.inf)
        logger.info("rel_grad_err_inf: %.5e", rel_grad_err_inf)
    else:
        rel_grad_err_inf = None

    # }}}

    # {{{ test tangential derivative

    if case.check_tangential_deriv and case.side != "scat":
        sym_tang_deriv_op = op.representation(
            sym_u,
            map_potentials=lambda p: sym.tangential_derivative(ambient_dim, p),
            qbx_forced_limit=case.side).as_scalar()

        tang_deriv_from_src = bind(places,
                                   sym_tang_deriv_op,
                                   auto_where=case.name)(
                                       actx,
                                       u=weighted_u,
                                       **case.knl_concrete_kwargs)
        tang_deriv_ref = bind(
            places,
            sym.tangential_derivative(ambient_dim, pot_src).as_scalar(),
            auto_where=("point_source", case.name))(actx,
                                                    charges=source_charges_dev,
                                                    **case.knl_concrete_kwargs)
示例#8
0
    def bc_term_to_operator_contrib(self, term, side, raw_potential_op,
            density, discrete):
        potential_op = raw_potential_op

        side_sign = self.side_to_sign[side]

        domain_outer, domain_inner, interface_id = \
                self.interfaces[term.i_interface]
        if side == self.side_in:
            K_expr = self.domain_K_exprs[domain_inner]  # noqa
            bc_coeff = term.coeff_inner
        elif side == self.side_out:
            K_expr = self.domain_K_exprs[domain_outer]  # noqa
            bc_coeff = term.coeff_outer
        else:
            raise ValueError("invalid value of 'side'")

        potential_op = potential_op(
                self.kernel, density, source=interface_id,
                k=K_expr)

        if term.direction == self.dir_none:
            if raw_potential_op is sym.S:
                jump_term = 0
            elif raw_potential_op is sym.D:
                jump_term = (side_sign*0.5) * discrete
            else:
                assert False, raw_potential_op
        elif term.direction == self.dir_normal:
            potential_op = sym.normal_derivative(
                    potential_op, interface_id)

            if raw_potential_op is sym.S:
                # S'
                jump_term = (-side_sign*0.5) * discrete
            elif raw_potential_op is sym.D:
                jump_term = 0
            else:
                assert False, raw_potential_op

        elif term.direction == self.dir_tangential:
            potential_op = sym.tangential_derivative(
                    raw_potential_op(
                        self.kernel, density, source=interface_id,
                        k=K_expr, qbx_forced_limit=side_sign),
                    interface_id).a.as_scalar()

            # Some of these may have jumps, but QBX does the dirty
            # work here by directly computing the limit.
            jump_term = 0

        else:
            raise ValueError("invalid direction")

        potential_op = (
                jump_term
                + self.get_sqrt_weight(interface_id)*potential_op)

        del jump_term

        contrib = bc_coeff * potential_op

        if (raw_potential_op is sym.D
                and term.direction == self.dir_normal):
            # FIXME The hypersingular part should perhaps be
            # treated specially to avoid cancellation.
            pass

        return contrib
示例#9
0
 def St(dom, density):  # noqa
     return sym.tangential_derivative(2, S(dom, density)).xproject(0)
示例#10
0
 def Tt(where, dom, density):  # noqa
     return sym.tangential_derivative(
             2, T(where, dom, density)).xproject(0)
示例#11
0
    if case.check_tangential_deriv and case.prob_side != "scat":
        bound_t_deriv_op = bind(
            qbx,
            op.representation(sym.var("u"),
                              map_potentials=partial(sym.tangential_derivative,
                                                     2),
                              qbx_forced_limit=loc_sign))

        # logger.info(bound_t_deriv_op.code)

        tang_deriv_from_src = bound_t_deriv_op(
            queue, u=weighted_u, **concrete_knl_kwargs).as_scalar().get()

        tang_deriv_ref = (bind((point_source, density_discr),
                               sym.tangential_derivative(2, pot_src))(
                                   queue,
                                   charges=source_charges_dev,
                                   **concrete_knl_kwargs).as_scalar().get())

        if 0:
            pt.plot(tang_deriv_ref.real)
            pt.plot(tang_deriv_from_src.real)
            pt.show()

        td_err = (tang_deriv_from_src - tang_deriv_ref)

        rel_td_err_inf = (la.norm(td_err, np.inf) /
                          la.norm(tang_deriv_ref, np.inf))

        logger.info("rel_td_err_inf: %g" % rel_td_err_inf)