Пример #1
0
def taylor_test(J, m, Jm, dJdm, HJm=None, seed=None, perturbation_direction=None, value=None, size=None):
    '''J must be a function that takes in a parameter value m and returns the value
       of the functional:

         func = J(m)

       Jm is the value of the function J at the parameter m.
       dJdm is the gradient of J evaluated at m, to be tested for correctness.

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the adjoint is working
       correctly.

       If HJm is not None, the Taylor test will also attempt to verify the
       correctness of the Hessian. HJm should be a callable which takes in a
       direction and returns the Hessian of the functional in that direction
       (i.e., takes in a vector and returns a vector). In that case, an additional
       Taylor remainder is computed, which should converge at order 3 if the Hessian
       is correct.'''

    from . import controls
    from .controls import ListControl

    info_blue("Running Taylor remainder convergence test ... ")

    if isinstance(m, list):
        m = ListControl(m)

    # Handle the multi-control case
    # We do this by performing a separate Taylor test for each control.
    if isinstance(m, controls.ListControl):
        return _taylor_test_multi_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value, size=size)
    else:
        return _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value, size=size)
Пример #2
0
def test_scalar_parameters_adjoint(J, a, dJda, seed=0.1):
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    functional_values = []
    f_direct = J(a)

    a = numpy.array([float(x) for x in a])
    dJda = numpy.array(dJda)

    perturbation_direction = a/5.0
    perturbation_sizes = [seed / (2**i) for i in range(5)]
    perturbations = [a * i for i in perturbation_sizes]
    for x in perturbations:
        da = [backend.Constant(a[i] + x[i]) for i in range(len(a))]
        functional_values.append(J(da))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    for i in range(len(perturbations)):
        remainder = abs(functional_values[i] - f_direct - numpy.dot(dJda, perturbations[i]))
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    return min(convergence_order(with_gradient))
Пример #3
0
def test_scalar_parameters_adjoint(J, a, dJda, seed=0.1):
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    functional_values = []
    f_direct = J(a)

    a = numpy.array([float(x) for x in a])
    dJda = numpy.array(dJda)

    perturbation_direction = a/5.0
    perturbation_sizes = [seed / (2**i) for i in range(5)]
    perturbations = [a * i for i in perturbation_sizes]
    for x in perturbations:
        da = [backend.Constant(a[i] + x[i]) for i in range(len(a))]
        functional_values.append(J(da))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    for i in range(len(perturbations)):
        remainder = abs(functional_values[i] - f_direct - numpy.dot(dJda, perturbations[i]))
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    return min(convergence_order(with_gradient))
Пример #4
0
def taylor_test(J, m, Jm, dJdm, HJm=None, seed=None, perturbation_direction=None, value=None):
    '''J must be a function that takes in a parameter value m and returns the value
       of the functional:

         func = J(m)

       Jm is the value of the function J at the parameter m.
       dJdm is the gradient of J evaluated at m, to be tested for correctness.

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the adjoint is working
       correctly.

       If HJm is not None, the Taylor test will also attempt to verify the
       correctness of the Hessian. HJm should be a callable which takes in a
       direction and returns the Hessian of the functional in that direction
       (i.e., takes in a vector and returns a vector). In that case, an additional
       Taylor remainder is computed, which should converge at order 3 if the Hessian
       is correct.'''

    info_blue("Running Taylor remainder convergence test ... ")

    if isinstance(m, list):
        m = ListControl(m)

    # Handle the multi-control case
    # We do this by performing a separate Taylor test for each control.
    if isinstance(m, controls.ListControl):
        return _taylor_test_multi_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value)
    else:
        return _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value)
Пример #5
0
def test_scalar_parameter_adjoint(J, a, dJda, seed=None):
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    functional_values = []
    f_direct = J(a)

    if seed is None:
        seed = float(a)/5.0
        if seed == 0.0:
            seed = 0.1

    perturbations = [seed / (2**i) for i in range(5)]

    for da in (backend.Constant(float(a) + x) for x in perturbations):
        functional_values.append(J(da))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    gradient_fd   = []
    for i in range(len(perturbations)):
        gradient_fd.append((functional_values[i] - f_direct)/perturbations[i])

        remainder = abs(functional_values[i] - f_direct - float(dJda)*perturbations[i])
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    info("Gradients (finite differencing): " + str(gradient_fd))
    info("Gradient (adjoint): " + str(dJda))

    return min(convergence_order(with_gradient))
Пример #6
0
def test_scalar_parameter_adjoint(J, a, dJda, seed=None):
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    functional_values = []
    f_direct = J(a)

    if seed is None:
        seed = float(a)/5.0
        if seed == 0.0:
            seed = 0.1

    perturbations = [seed / (2**i) for i in range(5)]

    for da in (backend.Constant(float(a) + x) for x in perturbations):
        functional_values.append(J(da))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    gradient_fd   = []
    for i in range(len(perturbations)):
        gradient_fd.append((functional_values[i] - f_direct)/perturbations[i])

        remainder = abs(functional_values[i] - f_direct - float(dJda)*perturbations[i])
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    info("Gradients (finite differencing): " + str(gradient_fd))
    info("Gradient (adjoint): " + str(dJda))

    return min(convergence_order(with_gradient))
Пример #7
0
def test_initial_condition_adjoint(J, ic, final_adjoint, seed=0.01, perturbation_direction=None):
    '''forward must be a function that takes in the initial condition (ic) as a backend.Function
       and returns the functional value by running the forward run:

         func = J(ic)

       final_adjoint is the adjoint associated with the initial condition
       (usually the last adjoint equation solved).

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the adjoint is working
       correctly.'''

    # We will compute the gradient of the functional with respect to the initial condition,
    # and check its correctness with the Taylor remainder convergence test.
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    # First run the problem unperturbed
    ic_copy = backend.Function(ic)
    f_direct = J(ic_copy)

    # Randomise the perturbation direction:
    if perturbation_direction is None:
        perturbation_direction = backend.Function(ic.function_space())
        compatibility.randomise(perturbation_direction)

    # Run the forward problem for various perturbed initial conditions
    functional_values = []
    perturbations = []
    perturbation_sizes = [seed/(2**i) for i in range(5)]
    for perturbation_size in perturbation_sizes:
        perturbation = backend.Function(perturbation_direction)
        vec = perturbation.vector()
        vec *= perturbation_size
        perturbations.append(perturbation)

        perturbed_ic = backend.Function(ic)
        vec = perturbed_ic.vector()
        vec += perturbation.vector()

        functional_values.append(J(perturbed_ic))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    adjoint_vector = final_adjoint.vector()

    with_gradient = []
    gradient_fd   = []
    for i in range(len(perturbations)):
        gradient_fd.append((functional_values[i] - f_direct)/perturbation_sizes[i])

        remainder = abs(functional_values[i] - f_direct - adjoint_vector.inner(perturbations[i].vector()))
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    info("Gradients (finite differencing): " + str(gradient_fd))
    info("Gradient (adjoint): " + str(adjoint_vector.inner(perturbation_direction.vector())))

    return min(convergence_order(with_gradient))
Пример #8
0
def taylor_test(J, m, Jm, dJdm, HJm=None, seed=None, perturbation_direction=None, value=None):
    '''J must be a function that takes in a parameter value m and returns the value
       of the functional:

         func = J(m)

       Jm is the value of the function J at the parameter m.
       dJdm is the gradient of J evaluated at m, to be tested for correctness.

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the adjoint is working
       correctly.

       If HJm is not None, the Taylor test will also attempt to verify the
       correctness of the Hessian. HJm should be a callable which takes in a
       direction and returns the Hessian of the functional in that direction
       (i.e., takes in a vector and returns a vector). In that case, an additional
       Taylor remainder is computed, which should converge at order 3 if the Hessian
       is correct.'''

    info_blue("Running Taylor remainder convergence test ... ")
    import controls

    if isinstance(m, list):
        m = ListControl(m)

    if isinstance(m, controls.ListControl):
        if perturbation_direction is None:
            perturbation_direction = [None] * len(m.controls)

        if value is None:
            value = [None] * len(m.controls)

        return min(taylor_test(J, m[i], Jm, dJdm[i], HJm, seed, perturbation_direction[i], value[i]) for i in range(len(m.controls)))

    def get_const(val):
        if isinstance(val, str):
            return float(constant.constant_values[val])
        else:
            return float(val)

    def get_value(param, value):
        if value is not None:
            return value
        else:
            try:
                return param.data()
            except libadjoint.exceptions.LibadjointErrorNeedValue:
                info_red("Do you need to pass forget=False to compute_gradient?")
                raise

    # First, compute perturbation sizes.
    seed_default = 0.01
    if seed is None:
        if isinstance(m, controls.ConstantControl):
            seed = get_const(m.a) / 5.0

            if seed == 0.0: seed = 0.1
        elif isinstance(m, controls.FunctionControl):
            ic = get_value(m, value)
            if len(ic.vector()) == 1: # our control is in R
                seed = float(ic) / 5.0
            else:
                seed = seed_default
        else:
            seed = seed_default

    perturbation_sizes = [seed/(2.0**i) for i in range(5)]

    # Next, compute the perturbation direction.
    if perturbation_direction is None:
        if isinstance(m, controls.ConstantControl):
            perturbation_direction = 1
        elif isinstance(m, controls.ConstantControls):
            perturbation_direction = numpy.array([get_const(x)/5.0 for x in m.v])
        elif isinstance(m, controls.FunctionControl):
            ic = get_value(m, value)
            perturbation_direction = backend.Function(ic.function_space())
            compatibility.randomise(perturbation_direction)
        else:
            raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to compute a perturbation direction")
    else:
        if isinstance(m, controls.FunctionControl):
            ic = get_value(m, value)

    # So now compute the perturbations:
    if not isinstance(perturbation_direction, backend.Function):
        perturbations = [x*perturbation_direction for x in perturbation_sizes]
    else:
        perturbations = []
        for x in perturbation_sizes:
            perturbation = backend.Function(perturbation_direction)
            vec = perturbation.vector()
            vec *= x
            perturbations.append(perturbation)

    # And now the perturbed inputs:
    if isinstance(m, controls.ConstantControl):
        pinputs = [backend.Constant(get_const(m.a) + x) for x in perturbations]
    elif isinstance(m, controls.ConstantControls):
        a = numpy.array([get_const(x) for x in m.v])

        def make_const(arr):
            return [backend.Constant(x) for x in arr]

        pinputs = [make_const(a + x) for x in perturbations]
    elif isinstance(m, controls.FunctionControl):
        pinputs = []
        for x in perturbations:
            pinput = backend.Function(x)
            vec = pinput.vector()
            vec += ic.vector()
            pinputs.append(pinput)

    # Issue 34: We must evaluate HJm before we evaluate the tape at the
    # perturbed controls below.
    if HJm is not None:
        HJm_values = []
        for perturbation in perturbations:
            HJmp = HJm(perturbation)
            HJm_values.append(HJmp)

    # At last: the common bit!
    functional_values = []
    for pinput in pinputs:
        Jp = J(pinput)
        functional_values.append(Jp)

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_J - Jm) for perturbed_J in functional_values]

    info("Taylor remainder without gradient information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without gradient information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    for i in range(len(perturbations)):
        if isinstance(m, controls.ConstantControl) or isinstance(m, controls.ConstantControls):
            remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i])
        else:
            remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i], ic=ic)
        with_gradient.append(remainder)

    if min(with_gradient + no_gradient) < 1e-16:
        warning("Warning: The Taylor remainders are close to machine precision (< %s). Try increasing the seed value in case the Taylor remainder test fails." % min(with_gradient + no_gradient))

    info("Taylor remainder with gradient information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with gradient information (should all be 2): " + str(convergence_order(with_gradient)))

    if HJm is not None:
        with_hessian = []
        if isinstance(m, controls.ConstantControl):
            for i in range(len(perturbations)):
                remainder = abs(functional_values[i] - Jm - float(dJdm)*perturbations[i] - 0.5*perturbations[i]*HJm_values[i])
                with_hessian.append(remainder)
        elif isinstance(m, controls.ConstantControls):
            for i in range(len(perturbations)):
                remainder = abs(functional_values[i] - Jm - numpy.dot(dJdm, perturbations[i]) - 0.5*numpy.dot(perturbations[i], HJm_values[i]))
                with_hessian.append(remainder)
        elif isinstance(m, controls.FunctionControl):
            for i in range(len(perturbations)):
                remainder = abs(functional_values[i] - Jm - dJdm.vector().inner(perturbations[i].vector()) - 0.5*perturbations[i].vector().inner(HJm_values[i].vector()))
                with_hessian.append(remainder)

        info("Taylor remainder with Hessian information: " + str(with_hessian))
        info("Convergence orders for Taylor remainder with Hessian information (should all be 3): " + str(convergence_order(with_hessian)))
        return min(convergence_order(with_hessian))
    else:
        return min(convergence_order(with_gradient))
Пример #9
0
def test_initial_condition_tlm(J, dJ, ic, seed=0.01, perturbation_direction=None):
    '''forward must be a function that takes in the initial condition (ic) as a backend.Function
       and returns the functional value by running the forward run:

         func = J(ic)

       final_adjoint is the tangent linear variable for the solution on which the functional depends
       (usually the last TLM equation solved).

       dJ must be the derivative of the functional with respect to its argument, evaluated and assembled at
       the unperturbed solution (a backend Vector).

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the TLM is working
       correctly.'''

    # We will compute the gradient of the functional with respect to the initial condition,
    # and check its correctness with the Taylor remainder convergence test.
    info_blue("Running Taylor remainder convergence analysis for the tangent linear model... ")
    import controls

    adj_var = adjglobals.adj_variables[ic]; adj_var.timestep = 0
    if not adjglobals.adjointer.variable_known(adj_var):
        info_red(str(adj_var) + " not known.")
        raise libadjoint.exceptions.LibadjointErrorInvalidInputs("Your initial condition must be the /exact same Function/ as the initial condition used in the forward model.")

    # First run the problem unperturbed
    ic_copy = backend.Function(ic)
    f_direct = J(ic_copy)

    # Randomise the perturbation direction:
    if perturbation_direction is None:
        perturbation_direction = backend.Function(ic.function_space())
        compatibility.randomise(perturbation_direction)

    # Run the forward problem for various perturbed initial conditions
    functional_values = []
    perturbations = []
    for perturbation_size in [seed/(2**i) for i in range(5)]:
        perturbation = backend.Function(perturbation_direction)
        vec = perturbation.vector()
        vec *= perturbation_size
        perturbations.append(perturbation)

        perturbed_ic = backend.Function(ic)
        vec = perturbed_ic.vector()
        vec += perturbation.vector()

        functional_values.append(J(perturbed_ic))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without tangent linear information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without tangent linear information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    for i in range(len(perturbations)):
        param = controls.FunctionControl(ic, perturbations[i])
        final_tlm = tlm_dolfin(param, forget=False).data
        remainder = abs(functional_values[i] - f_direct - final_tlm.vector().inner(dJ))
        with_gradient.append(remainder)

    info("Taylor remainder with tangent linear information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with tangent linear information (should all be 2): " + str(convergence_order(with_gradient)))

    return min(convergence_order(with_gradient))
Пример #10
0
def test_initial_condition_adjoint(J, ic, final_adjoint, seed=0.01, perturbation_direction=None):
    '''forward must be a function that takes in the initial condition (ic) as a backend.Function
       and returns the functional value by running the forward run:

         func = J(ic)

       final_adjoint is the adjoint associated with the initial condition
       (usually the last adjoint equation solved).

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the adjoint is working
       correctly.'''

    from . import function

    # We will compute the gradient of the functional with respect to the initial condition,
    # and check its correctness with the Taylor remainder convergence test.
    info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ")

    # First run the problem unperturbed
    ic_copy = ic.copy(deepcopy=True)
    f_direct = J(ic_copy)

    # Randomise the perturbation direction:
    if perturbation_direction is None:
        perturbation_direction = function.Function(ic.function_space())
        compatibility.randomise(perturbation_direction)

    # Run the forward problem for various perturbed initial conditions
    functional_values = []
    perturbations = []
    perturbation_sizes = [seed/(2**i) for i in range(5)]
    for perturbation_size in perturbation_sizes:
        perturbation = perturbation_direction.copy(deepcopy=True)
        vec = perturbation.vector()
        vec *= perturbation_size
        perturbations.append(perturbation)

        perturbed_ic = ic.copy(deepcopy=True)
        vec = perturbed_ic.vector()
        vec += perturbation.vector()

        functional_values.append(J(perturbed_ic))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without adjoint information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient)))

    adjoint_vector = final_adjoint.vector()

    with_gradient = []
    gradient_fd   = []
    for i in range(len(perturbations)):
        gradient_fd.append((functional_values[i] - f_direct)/perturbation_sizes[i])

        remainder = abs(functional_values[i] - f_direct - adjoint_vector.inner(perturbations[i].vector()))
        with_gradient.append(remainder)

    info("Taylor remainder with adjoint information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient)))

    info("Gradients (finite differencing): " + str(gradient_fd))
    info("Gradient (adjoint): " + str(adjoint_vector.inner(perturbation_direction.vector())))

    return min(convergence_order(with_gradient))
Пример #11
0
def test_initial_condition_tlm(J, dJ, ic, seed=0.01, perturbation_direction=None):
    '''forward must be a function that takes in the initial condition (ic) as a backend.Function
       and returns the functional value by running the forward run:

         func = J(ic)

       final_adjoint is the tangent linear variable for the solution on which the functional depends
       (usually the last TLM equation solved).

       dJ must be the derivative of the functional with respect to its argument, evaluated and assembled at
       the unperturbed solution (a backend Vector).

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the TLM is working
       correctly.'''

    import controls

    # We will compute the gradient of the functional with respect to the initial condition,
    # and check its correctness with the Taylor remainder convergence test.
    info_blue("Running Taylor remainder convergence analysis for the tangent linear model... ")

    adj_var = adjglobals.adj_variables[ic]; adj_var.timestep = 0
    if not adjglobals.adjointer.variable_known(adj_var):
        info_red(str(adj_var) + " not known.")
        raise libadjoint.exceptions.LibadjointErrorInvalidInputs("Your initial condition must be the /exact same Function/ as the initial condition used in the forward model.")

    # First run the problem unperturbed
    ic_copy = backend.Function(ic)
    f_direct = J(ic_copy)

    # Randomise the perturbation direction:
    if perturbation_direction is None:
        perturbation_direction = backend.Function(ic.function_space())
        compatibility.randomise(perturbation_direction)

    # Run the forward problem for various perturbed initial conditions
    functional_values = []
    perturbations = []
    for perturbation_size in [seed/(2**i) for i in range(5)]:
        perturbation = backend.Function(perturbation_direction)
        vec = perturbation.vector()
        vec *= perturbation_size
        perturbations.append(perturbation)

        perturbed_ic = backend.Function(ic)
        vec = perturbed_ic.vector()
        vec += perturbation.vector()

        functional_values.append(J(perturbed_ic))

    # First-order Taylor remainders (not using adjoint)
    no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values]

    info("Taylor remainder without tangent linear information: " + str(no_gradient))
    info("Convergence orders for Taylor remainder without tangent linear information (should all be 1): " + str(convergence_order(no_gradient)))

    with_gradient = []
    for i in range(len(perturbations)):
        param = controls.FunctionControl(ic, perturbations[i])
        final_tlm = tlm_dolfin(param, forget=False).data
        remainder = abs(functional_values[i] - f_direct - final_tlm.vector().inner(dJ))
        with_gradient.append(remainder)

    info("Taylor remainder with tangent linear information: " + str(with_gradient))
    info("Convergence orders for Taylor remainder with tangent linear information (should all be 2): " + str(convergence_order(with_gradient)))

    return min(convergence_order(with_gradient))