Пример #1
0
def test_gradient_array(J, dJdx, x, seed = 0.01, perturbation_direction = None):
    '''Checks the correctness of the derivative dJ.
       x must be an array that specifies at which point in the parameter space
       the gradient is to be checked, and dJdx must be an array containing the gradient.
       The function J(x) must return the functional value.

       This function returns the order of convergence of the Taylor
       series remainder, which should be 2 if the gradient is correct.'''

    # 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("Running Taylor remainder convergence analysis to check the gradient ... ")

    # First run the problem unperturbed
    j_direct = J(x)

    # Randomise the perturbation direction:
    if perturbation_direction is None:
        perturbation_direction = x.copy()
        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() * perturbation_size
        perturbations.append(perturbation)

        perturbed_x = x.copy() + perturbation
        functional_values.append(J(perturbed_x))

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

    info("Absolute functional evaluation differences: %s" % str(no_gradient))
    info("Convergence orders for Taylor remainder without adjoint information (should all be 1): %s" % str(convergence_order(no_gradient)))

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

    if min(with_gradient + no_gradient) < 1e-16:
        info("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("Absolute functional evaluation differences with adjoint: %s" % str(with_gradient))
    info("Convergence orders for Taylor remainder with adjoint information (should all be 2): %s" % str(convergence_order(with_gradient)))

    return min(convergence_order(with_gradient))
Пример #2
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))
Пример #3
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))
Пример #4
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))
Пример #5
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))