Ejemplo n.º 1
0
def taylor_test_expression(exp, V, seed=None):
    """
    Performs a Taylor test of an Expression with dependencies.

    exp: The expression to test
    V: A suitable function space on which the expression will be projected.
    seed: The initial perturbation coefficient for the taylor test.

    Warning: This function resets the adjoint tape! """
    from . import drivers, functional, projection
    from .controls import Control

    adjglobals.adj_reset()

    # Annotate test model
    s = projection.project(exp, V, annotate=True)
    mesh = V.mesh()

    Jform = s**2*backend.dx + exp*backend.dx(domain=mesh)

    J = functional.Functional(Jform)
    J0 = backend.assemble(Jform)

    controls = [Control(c) for c in exp.dependencies]
    dJd0 = drivers.compute_gradient(J, controls, forget=False)

    for i in range(len(controls)):
        def Jfunc(new_val):
            dep = exp.dependencies[i]

            # Remember the old dependency value for later
            old_val = float(dep)

            # Compute the functional value
            dep.assign(new_val)
            s = projection.project(exp, V, annotate=False)
            out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh))

            # Restore the old dependency value
            dep.assign(old_val)

            return out

        #HJ = hessian(J, controls[i], warn=False)
        #minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], HJm=HJ)
        minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], seed=seed)

        if math.isnan(minconv):
            warning("Convergence order is not a number. Assuming that you \
have a linear or constant constraint dependency (e.g. check that the Taylor \
remainder are all 0).")
        else:
            if not minconv > 1.9:
                raise Exception("The Taylor test failed when checking the \
derivative with respect to the %i'th dependency." % (i+1))

    adjglobals.adj_reset()
Ejemplo n.º 2
0
    def basic_solve(self, var, b):
        if isinstance(self.data, IdentityMatrix):
            x=b.duplicate()
            x.axpy(1.0, b)
            if isinstance(x.data, ufl.Form):
                x = Vector(backend.Function(x.fn_space, backend.assemble(x.data)))
        else:
            if var.type in ['ADJ_TLM', 'ADJ_ADJOINT', 'ADJ_SOA']:
                dirichlet_bcs = [utils.homogenize(bc) for bc in self.bcs if isinstance(bc, backend.DirichletBC)]
                other_bcs  = [bc for bc in self.bcs if not isinstance(bc, backend.DirichletBC)]
                bcs = dirichlet_bcs + other_bcs
            else:
                bcs = self.bcs

            test = self.test_function()

            #FIXME: This is a hack, the test and trial function should carry
            # the MultiMeshFunctionSpace as an attribute
            if hasattr(test, '_V_multi'):
                x = Vector(backend.MultiMeshFunction(test._V_multi))
            else:
                x = Vector(backend.Function(test.function_space()))

            #print "b.data is a %s in the solution of %s" % (b.data.__class__, var)
            if b.data is None and not hasattr(b, 'nonlinear_form'):
                # This means we didn't get any contribution on the RHS of the adjoint system. This could be that the
                # simulation ran further ahead than when the functional was evaluated, or it could be that the
                # functional is set up incorrectly.
                backend.warning("Warning: got zero RHS for the solve associated with variable %s" % var)
            elif isinstance(b.data, backend.Function):

                assembled_lhs = self.assemble_data()
                [bc.apply(assembled_lhs) for bc in bcs]
                assembled_rhs = compatibility.assembled_rhs(b)
                [bc.apply(assembled_rhs) for bc in bcs]

                wrap_solve(assembled_lhs, x.data, assembled_rhs, self.solver_parameters)
            else:
                if hasattr(b, 'nonlinear_form'): # was a nonlinear solve
                    x = compatibility.assign_function_to_vector(x, b.nonlinear_u, function_space = test.function_space())
                    F = backend.replace(b.nonlinear_form, {b.nonlinear_u: x.data})
                    J = backend.replace(b.nonlinear_J, {b.nonlinear_u: x.data})
                    try:
                        compatibility.solve(F == 0, x.data, b.nonlinear_bcs, J=J, solver_parameters=self.solver_parameters)
                    except RuntimeError as rte:
                        x.data.vector()[:] = float("nan")

                else:
                    assembled_lhs = self.assemble_data()
                    [bc.apply(assembled_lhs) for bc in bcs]
                    assembled_rhs = wrap_assemble(b.data, test)
                    [bc.apply(assembled_rhs) for bc in bcs]

                    wrap_solve(assembled_lhs, x.data, assembled_rhs, self.solver_parameters)

        return x
Ejemplo n.º 3
0
def taylor_test_expression(exp, V):
    """
    Performs a Taylor test of an Expression with dependencies.

    exp: The expression to test
    V: A suitable function space on which the expression will be projected.

    Warning: This function resets the adjoint tape! """

    adjglobals.adj_reset()

    # Annotate test model
    s = projection.project(exp, V, annotate=True)
    mesh = V.mesh()

    Jform = s**2*backend.dx + exp*backend.dx(domain=mesh)

    J = functional.Functional(Jform)
    J0 = backend.assemble(Jform)

    deps = exp.dependencies()
    controls = [Control(c) for c in deps]
    dJd0 = drivers.compute_gradient(J, controls, forget=False)

    for i in range(len(controls)):
        def Jfunc(new_val):
            dep = exp.dependencies()[i]

            # Remember the old dependency value for later
            old_val = float(dep)

            # Compute the functional value
            dep.assign(new_val)
            s = projection.project(exp, V, annotate=False)
            out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh))

            # Restore the old dependency value
            dep.assign(old_val)

            return out

        #HJ = hessian(J, controls[i], warn=False)
        #minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], HJm=HJ)
        minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i])

        if math.isnan(minconv):
            warning("Convergence order is not a number. Assuming that you \
have a linear or constant constraint dependency (e.g. check that the Taylor \
remainder are all 0).")
        else:
            if not minconv > 1.9:
                raise Exception, "The Taylor test failed when checking the \
derivative with respect to the %i'th dependency." % (i+1)

    adjglobals.adj_reset()
Ejemplo n.º 4
0
    def caching_solve(self, var, b):
        if isinstance(self.data, IdentityMatrix):
            output = b.duplicate()
            output.axpy(1.0, b)
            if isinstance(output.data, ufl.Form):
                output = Vector(backend.Function(output.fn_space, backend.assemble(output.data)))
        elif b.data is None:
            backend.warning("Warning: got zero RHS for the solve associated with variable %s" % var)
            output = Vector(backend.Function(self.test_function().function_space()))
        else:
            dirichlet_bcs = [utils.homogenize(bc) for bc in self.bcs if isinstance(bc, backend.DirichletBC)]
            other_bcs  = [bc for bc in self.bcs if not isinstance(bc, backend.DirichletBC)]
            bcs = dirichlet_bcs + other_bcs

            output = Vector(backend.Function(self.test_function().function_space()))
            #print "b.data is a %s in the solution of %s" % (b.data.__class__, var)
            if backend.parameters["adjoint"]["symmetric_bcs"] and backend.__version__ > '1.2.0':
                assembler = backend.SystemAssembler(self.data, b.data, bcs)
                assembled_rhs = backend.Vector()
                assembler.assemble(assembled_rhs)
            elif isinstance(b.data, ufl.Form):
                assembled_rhs = wrap_assemble(b.data, self.test_function())
            else:
                if backend.__name__ == "dolfin":
                    assembled_rhs = b.data.vector()
                else:
                    assembled_rhs = b.data
            [bc.apply(assembled_rhs) for bc in bcs]

            if not var in caching.lu_solvers:
                if backend.parameters["adjoint"]["debug_cache"]:
                    backend.info_red("Got a cache miss for %s" % var)

                if backend.parameters["adjoint"]["symmetric_bcs"] and backend.__version__ > '1.2.0':
                    assembled_lhs = backend.Matrix()
                    assembler.assemble(assembled_lhs)
                else:
                    assembled_lhs = self.assemble_data()
                    [bc.apply(assembled_lhs) for bc in bcs]

                caching.lu_solvers[var] = compatibility.LUSolver(assembled_lhs, "mumps")
                caching.lu_solvers[var].parameters["reuse_factorization"] = True
            else:
                if backend.parameters["adjoint"]["debug_cache"]:
                    backend.info_green("Got a cache hit for %s" % var)

            caching.lu_solvers[var].solve(output.data.vector(), assembled_rhs)

        return output
Ejemplo n.º 5
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))
Ejemplo n.º 6
0
def _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value, size=None):
    from . import function, controls

    # Default to five runs/perturbations is none given
    if size is None:
        size = 5
    
    # Check inputs
    if not isinstance(m, libadjoint.Parameter):
        raise ValueError("m must be a valid control instance.")

    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(size)]

    # 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)

            # Check for MultiMeshFunction_space
            if isinstance(ic.function_space(), compatibility.multi_mesh_function_space_type):
                perturbation_direction = backend.MultiMeshFunction(ic.function_space())
            else:
                perturbation_direction = function.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)
        elif isinstance(m, controls.ConstantControl):
            perturbation_direction = float(perturbation_direction)

    # So now compute the perturbations:
    if not isinstance(perturbation_direction, (backend.Function, backend.MultiMeshFunction)):
        perturbations = [x*perturbation_direction for x in perturbation_sizes]
    else:
        perturbations = []
        for x in perturbation_sizes:
            perturbation = perturbation_direction.copy(deepcopy=True)
            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 = x.copy(deepcopy=True)
            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))
Ejemplo n.º 7
0
def _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value):
    import function

    # Check inputs
    if not isinstance(m, libadjoint.Parameter):
        raise ValueError, "m must be a valid control instance."

    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)

            # Check for MultiMeshFunction_space
            if isinstance(ic.function_space(), compatibility.multi_mesh_function_space_type):
                perturbation_direction = backend.MultiMeshFunction(ic.function_space())
            else:
                perturbation_direction = function.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)
        elif isinstance(m, controls.ConstantControl):
            perturbation_direction = float(perturbation_direction)

    # So now compute the perturbations:
    if not isinstance(perturbation_direction, (backend.Function, backend.MultiMeshFunction)):
        perturbations = [x*perturbation_direction for x in perturbation_sizes]
    else:
        perturbations = []
        for x in perturbation_sizes:
            perturbation = perturbation_direction.copy(deepcopy=True)
            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 = x.copy(deepcopy=True)
            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))