def hessian(self, m_dot, project=False):
        """ Evaluates the Hessian action at the most recently evaluated control
        value in direction m_dot.

	Args:
            m_dot: The direction in control space in which to compute the
                Hessian. Must be of the same type as the Control (e.g. Function,
                Constant or lists of latter).

            project (Optional[bool]): If True, the returned value will be the L2
                Riesz representer, if False it will be the l2 Riesz representative.
                The L2 projection requires one additional linear solve.  Defaults to
                False.

	Returns:
	    The directional second derivative. The returned type is the same as the control
            type.

        Note: Hessian evaluations never delete the forward state.
        """

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls] + [m_dot])
            fnspaces = [
                p.data().function_space()
                if isinstance(p.data(), Function) else None
                for p in self.controls
            ]

            if hash in self._cache["hessian_cache"]:
                info_green("Got a Hessian cache hit.")
                return cache_load(self._cache["hessian_cache"][hash], fnspaces)
            else:
                info_red("Got a Hessian cache miss")

        # Compute the Hessian action by solving the second order adjoint equations
        Hm = self.H(m_dot, project=project)

        # Apply the scaling factor
        scaled_Hm = utils.scale(Hm, self.scale)

        # Call callback
        control_data = [p.data() for p in self.controls]
        if self.current_func_value is not None:
            current_func_value = self.scale * self.current_func_value
        else:
            current_func_value = None

        self.hessian_cb(current_func_value,
                        delist(control_data, list_type=self.controls), m_dot,
                        scaled_Hm)

        # Cache the result
        if self.cache is not None:
            self._cache["hessian_cache"][hash] = cache_store(
                scaled_Hm, self.cache)

        return scaled_Hm
Example #2
0
def get_residual(i):
    from .adjrhs import adj_get_forward_equation
    (fwd_var, lhs, rhs) = adj_get_forward_equation(i)

    if isinstance(lhs, adjlinalg.IdentityMatrix):
        return None

    fn_space = ufl.algorithms.extract_arguments(lhs)[0].function_space()
    x = backend.Function(fn_space)

    if rhs == 0:
        form = lhs
        x = fwd_var.nonlinear_u
    else:
        form = backend.action(lhs, x) - rhs

    try:
        y = adjglobals.adjointer.get_variable_value(fwd_var).data
    except libadjoint.exceptions.LibadjointErrorNeedValue:
        info_red(
            "Warning: recomputing forward solution; please report this script on launchpad"
        )
        y = adjglobals.adjointer.get_forward_solution(i)[1].data

    form = backend.replace(form, {x: y})

    return form
Example #3
0
    def assemble_data(self):
        assert not isinstance(self.data, IdentityMatrix)
        if backend.__name__ == "firedrake":
            # Firedrake specifies assembled matrix type as part of the
            # solver parameters.
            mat_type = self.solver_parameters.get("mat_type")
            assemble = lambda x: backend.assemble(self.data, mat_type=mat_type)
        else:
            assemble = backend.assemble
        if not self.cache:
            if hasattr(self.data.arguments()[0], '_V_multi'):
                return backend.assemble_multimesh(self.data)
            else:
                return backend.assemble(self.data)

        else:
            if self.data in caching.assembled_adj_forms:
                if backend.parameters["adjoint"]["debug_cache"]:
                    backend.info_green("Got an assembly cache hit")
                return caching.assembled_adj_forms[self.data]
            else:
                if backend.parameters["adjoint"]["debug_cache"]:
                    backend.info_red("Got an assembly cache miss")

                if hasattr(self.data.arguments()[0], '_V_multi'):
                    M = backend.assemble_multimesh(self.data)
                else:
                    M = backend.assemble(self.data)

                caching.assembled_adj_forms[self.data] = M
                return M
Example #4
0
def get_residual(i):
    from adjrhs import adj_get_forward_equation
    (fwd_var, lhs, rhs) = adj_get_forward_equation(i)

    if isinstance(lhs, adjlinalg.IdentityMatrix):
        return None

    fn_space = ufl.algorithms.extract_arguments(lhs)[0].function_space()
    x = backend.Function(fn_space)

    if rhs == 0:
        form = lhs
        x = fwd_var.nonlinear_u
    else:
        form = backend.action(lhs, x) - rhs

    try:
        y = adjglobals.adjointer.get_variable_value(fwd_var).data
    except libadjoint.exceptions.LibadjointErrorNeedValue:
        info_red("Warning: recomputing forward solution; please report this script on launchpad")
        y = adjglobals.adjointer.get_forward_solution(i)[1].data

    form = backend.replace(form, {x: y})

    return form
Example #5
0
    def derivative_action(self, dependencies, values, variable, contraction_vector, hermitian):

        # If you want to apply boundary conditions symmetrically in the adjoint
        # -- and you often do --
        # then we need to have a UFL representation of all the terms in the adjoint equation.
        # However!
        # Since UFL cannot represent the identity map,
        # we need to find an f such that when
        # assemble(inner(f, v)*dx)
        # we get the contraction_vector.data back.
        # This involves inverting a mass matrix.

        if backend.parameters["adjoint"]["symmetric_bcs"] and backend.__version__ <= '1.2.0':
            backend.info_red("Warning: symmetric BC application requested but unavailable in dolfin <= 1.2.0.")

        if backend.parameters["adjoint"]["symmetric_bcs"] and backend.__version__ > '1.2.0':

            V = contraction_vector.data.function_space()
            v = backend.TestFunction(V)

            if str(V) not in adjglobals.fsp_lu:
                u = backend.TrialFunction(V)
                A = backend.assemble(backend.inner(u, v)*backend.dx)
                lusolver = backend.LUSolver(A, "mumps")
                lusolver.parameters["symmetric"] = True
                lusolver.parameters["reuse_factorization"] = True
                adjglobals.fsp_lu[str(V)] = lusolver
            else:
                lusolver = adjglobals.fsp_lu[str(V)]

            riesz = backend.Function(V)
            lusolver.solve(riesz.vector(), contraction_vector.data.vector())
            return adjlinalg.Vector(backend.inner(riesz, v)*backend.dx)
        else:
            return adjlinalg.Vector(contraction_vector.data)
Example #6
0
  def solve(self, var, b):
    timer = backend.Timer("Matrix-free solver")
    solver = backend.PETScKrylovSolver(*self.solver_parameters)
    solver.parameters.update(self.parameters)

    x = backend.Function(self.fn_space)
    if b.data is None:
      backend.info_red("Warning: got zero RHS for the solve associated with variable %s" % var)
      return adjlinalg.Vector(x)

    if isinstance(b.data, backend.Function):
      rhs = b.data.vector().copy()
    else:
      rhs = backend.assemble(b.data)

    if var.type in ['ADJ_TLM', 'ADJ_ADJOINT']:
      self.bcs = [backend.homogenize(bc) for bc in self.bcs if isinstance(bc, backend.cpp.DirichletBC)] + [bc for bc in self.bcs if not isinstance(bc, backend.DirichletBC)]

    for bc in self.bcs:
      bc.apply(rhs)

    if self.operators[1] is not None: # we have a user-supplied preconditioner
      solver.set_operators(self.data, self.operators[1])
      solver.solve(backend.down_cast(x.vector()), backend.down_cast(rhs))
    else:
      solver.solve(self.data, backend.down_cast(x.vector()), backend.down_cast(rhs))

    timer.stop()
    return adjlinalg.Vector(x)
    def derivative(self, m_array=None, taylor_test=False, seed=0.001, forget=True, project=False):
        ''' An implementation of the reduced functional derivative evaluation
            that accepts the controls as an array of scalars. If no control values are given,
            the result is derivative at the last forward run.
            If taylor_test = True, the derivative is automatically verified
            by the Taylor remainder convergence test. The perturbation direction
            is random and the perturbation size can be controlled with the seed argument.
            '''

        # In the case that the control values have changed since the last forward run,
        # we first need to rerun the forward model with the new controls to have the
        # correct forward solutions
        m = [p.data() for p in self.controls]
        if m_array is not None and (m_array != self.get_global(m)).any():
            info_red("Rerunning forward model before computing derivative")
            self(m_array)

        dJdm = self.__base_derivative__(forget=forget, project=project)

        if project:
            dJdm_global = self.get_global(dJdm)
        else:
            dJdm_global = get_global(dJdm)

        # Perform the gradient test
        if taylor_test:
            minconv = utils.test_gradient_array(self.__call__, self.scale * dJdm_global, m_array,
                                                seed = seed)
            if minconv < 1.9:
                raise RuntimeWarning, "A gradient test failed during execution."
            else:
                info("Gradient test successful.")
            self(m_array)

        return dJdm_global
Example #8
0
    def __init__(self, J, m, warn=True):
        self.J = J

        self.enlisted_controls = enlist(m)
        self.m = ListControl(self.enlisted_controls)

        if warn:
            backend.info_red("Warning: Hessian computation is still experimental and is known to not work for some problems. Please Taylor test thoroughly.")
Example #9
0
 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
Example #10
0
 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
    def hessian(self, m_dot, project=False):
        """ Evaluates the Hessian action at the most recently evaluated control
        value in direction m_dot.

	Args:
            m_dot: The direction in control space in which to compute the
                Hessian. Must be of the same type as the Control (e.g. Function,
                Constant or lists of latter).

            project (Optional[bool]): If True, the returned value will be the L2
                Riesz representer, if False it will be the l2 Riesz representative.
                The L2 projection requires one additional linear solve.  Defaults to
                False.

	Returns:
	    The directional second derivative. The returned type is the same as the control
            type.

        Note: Hessian evaluations never delete the forward state.
        """

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls] + [m_dot])
            fnspaces = [p.data().function_space() if isinstance(p.data(),
                Function) else None for p in self.controls]

            if hash in self._cache["hessian_cache"]:
                info_green("Got a Hessian cache hit.")
                return cache_load(self._cache["hessian_cache"][hash], fnspaces)
            else:
                info_red("Got a Hessian cache miss")

        # Compute the Hessian action by solving the second order adjoint equations
        Hm = self.H(m_dot, project=project)

        # Apply the scaling factor
        scaled_Hm = utils.scale(Hm, self.scale)

        # Call callback
        control_data = [p.data() for p in self.controls]
        if self.current_func_value is not None:
            current_func_value = self.scale * self.current_func_value
        else:
            current_func_value = None

        self.hessian_cb(current_func_value,
                        delist(control_data, list_type=self.controls),
                        m_dot, scaled_Hm)

        # Cache the result
        if self.cache is not None:
            self._cache["hessian_cache"][hash] = cache_store(scaled_Hm, self.cache)

        return scaled_Hm
Example #12
0
    def __init__(self, J, m, warn=True):
        self.J = J

        self.enlisted_controls = enlist(m)
        self.m = ListControl(self.enlisted_controls)

        if warn:
            backend.info_red(
                "Warning: Hessian computation is still experimental and is known to not work for some problems. Please Taylor test thoroughly."
            )
Example #13
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()
            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.info_red("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.vector(), 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]

                    if backend.__name__ == "dolfin":
                        wrap_solve(assembled_lhs, x.data.vector(), assembled_rhs, self.solver_parameters)
                    else:
                        wrap_solve(assembled_lhs, x.data, assembled_rhs, self.solver_parameters)

        return x
Example #14
0
    def __init__(self, value, cell=None, name=None):
        backend.Constant.__init__(self, value, cell)
        if name is None:
            name = hash(self)

        self.adj_name = name

        if name in constant_values:
            backend.info_red("Warning: redefing constant with name %s" % name)

        constant_values[name] = value
        constant_objects[name] = self
Example #15
0
  def __init__(self, value, cell=None, name=None):
    backend.Constant.__init__(self, value, cell)
    if name is None:
      name = hash(self)

    self.adj_name = name

    if name in constant_values:
      backend.info_red("Warning: redefing constant with name %s" % name)

    constant_values[name] = value
    constant_objects[name] = self
Example #16
0
    def __call__(self, adjointer, timestep, dependencies, values):

        functional_value = self._substitute_form(adjointer, timestep, dependencies, values)

        if functional_value is not None:
            args = ufl.algorithms.extract_arguments(functional_value)
            if len(args) > 0:
                backend.info_red("The form passed into Functional must be rank-0 (a scalar)! You have passed in a rank-%s form." % len(args))
                raise libadjoint.exceptions.LibadjointErrorInvalidInputs

            return backend.assemble(functional_value)
        else:
            return 0.0
Example #17
0
    def __init__(self, J, m, warn=True):
        self.J = J
        self.m = m

        if warn:
            backend.info_red("Warning: Hessian computation is still experimental and is known to not work for some problems. Please Taylor test thoroughly.")

        if not isinstance(m, (FunctionControl, ConstantControl)):
            error_msg = "Sorry, Hessian computation only works for FunctionControl \
                         and ConstantControl so far."
            raise libadjoint.exceptions.LibadjointErrorNotImplemented(error_msg)

        self.update(m)
Example #18
0
    def __init__(self, timeform, verbose=False, name=None):

        if isinstance(timeform, ufl.form.Form):
            if adjglobals.adjointer.adjointer.ntimesteps > 1:
                backend.info_red("You are using a steady-state functional (without the *dt term) in a time-dependent simulation.\ndolfin-adjoint will assume that you want to evaluate the functional at the end of time.")
            timeform = timeform*dt[FINISH_TIME]

            if not timeform.is_functional():
                raise Exception, "Your functional must have rank 0, but is has rank >0"

        self.timeform = timeform
        self.verbose = verbose
        self.name = name
Example #19
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
    def derivative(self, forget=True, project=False):
        ''' Evaluates the derivative of the reduced functional for the most
        recently evaluated control value. '''

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls])
            fnspaces = [p.data().function_space() if isinstance(p.data(),
                Function) else None for p in self.controls]

            if hash in self._cache["derivative_cache"]:
                info_green("Got a derivative cache hit.")
                return cache_load(self._cache["derivative_cache"][hash], fnspaces)

        # Call callback
        values = [p.data() for p in self.controls]
        self.derivative_cb_pre(delist(values, list_type=self.controls))

        # Compute the gradient by solving the adjoint equations
        dfunc_value = drivers.compute_gradient(self.functional, self.controls, forget=forget, project=project)
        dfunc_value = enlist(dfunc_value)

        # Reset the checkpointing state in dolfin-adjoint
        adjointer.reset_revolve()

        # Apply the scaling factor
        scaled_dfunc_value = [utils.scale(df, self.scale) for df in list(dfunc_value)]

        # Call callback
        # We might have forgotten the control values already,
        # in which case we can only return Nones
        values = []
        for c in self.controls:
            try:
                values.append(p.data())
            except libadjoint.exceptions.LibadjointErrorNeedValue:
                values.append(None)
        if self.current_func_value is not None:
            self.derivative_cb_post(self.scale * self.current_func_value,
                    delist(scaled_dfunc_value, list_type=self.controls),
                    delist(values, list_type=self.controls))

        # Cache the result
        if self.cache is not None:
            info_red("Got a derivative cache miss")
            self._cache["derivative_cache"][hash] = cache_store(scaled_dfunc_value, self.cache)

        return scaled_dfunc_value
Example #21
0
 def assemble_data(self):
     assert not isinstance(self.data, IdentityMatrix)
     if not self.cache:
         return backend.assemble(self.data)
     else:
         if self.data in caching.assembled_adj_forms:
             if backend.parameters["adjoint"]["debug_cache"]:
                 backend.info_green("Got an assembly cache hit")
             return caching.assembled_adj_forms[self.data]
         else:
             if backend.parameters["adjoint"]["debug_cache"]:
                 backend.info_red("Got an assembly cache miss")
             M = backend.assemble(self.data)
             caching.assembled_adj_forms[self.data] = M
             return M
Example #22
0
    def derivative_action(self, dependencies, values, variable,
                          contraction_vector, hermitian):
        idx = dependencies.index(variable)

        # If you want to apply boundary conditions symmetrically in the adjoint
        # -- and you often do --
        # then we need to have a UFL representation of all the terms in the adjoint equation.
        # However!
        # Since UFL cannot represent the identity map,
        # we need to find an f such that when
        # assemble(inner(f, v)*dx)
        # we get the contraction_vector.data back.
        # This involves inverting a mass matrix.

        if backend.parameters["adjoint"][
                "symmetric_bcs"] and backend.__version__ <= '1.2.0':
            backend.info_red(
                "Warning: symmetric BC application requested but unavailable in dolfin <= 1.2.0."
            )

        if backend.parameters["adjoint"][
                "symmetric_bcs"] and backend.__version__ > '1.2.0':

            V = contraction_vector.data.function_space()
            v = backend.TestFunction(V)

            if str(V) not in adjglobals.fsp_lu:
                u = backend.TrialFunction(V)
                A = backend.assemble(backend.inner(u, v) * backend.dx)
                solver = "mumps" if "mumps" in backend.lu_solver_methods(
                ).keys() else "default"
                lusolver = backend.LUSolver(A, solver)
                lusolver.parameters["symmetric"] = True
                lusolver.parameters["reuse_factorization"] = True
                adjglobals.fsp_lu[str(V)] = lusolver
            else:
                lusolver = adjglobals.fsp_lu[str(V)]

            riesz = backend.Function(V)
            lusolver.solve(
                riesz.vector(),
                self.weights[idx] * contraction_vector.data.vector())
            out = (backend.inner(riesz, v) * backend.dx)
        else:
            out = backend.Function(self.fn_space)
            out.assign(self.weights[idx] * contraction_vector.data)

        return adjlinalg.Vector(out)
Example #23
0
    def __init__(self, data, zero=False, fn_space=None):


        self.data=data
        if not (self.data is None or isinstance(self.data, backend.Function) or isinstance(self.data, ufl.Form)):
            backend.info_red("Got " + str(self.data.__class__) + " as input to the Vector() class. Don't know how to handle that.")
            raise AssertionError

        # self.zero is true if we can prove that the vector is zero.
        if data is None:
            self.zero=True
        else:
            self.zero=zero

        if fn_space is not None:
            self.fn_space = fn_space
Example #24
0
    def __init__(self, timeform, verbose=False, name=None):

        if isinstance(timeform, ufl.form.Form):
            if adjglobals.adjointer.adjointer.ntimesteps > 1:
                backend.info_red(
                    "You are using a steady-state functional (without the *dt term) in a time-dependent simulation.\ndolfin-adjoint will assume that you want to evaluate the functional at the end of time."
                )
            timeform = timeform * dt[FINISH_TIME]

            if not timeform.is_functional():
                raise Exception(
                    "Your functional must have rank 0, but is has rank >0")

        self.timeform = timeform
        self.verbose = verbose
        self.name = name
Example #25
0
    def derivative(self, adjointer, variable, dependencies, values):

        functional_value = None
        for timestep in self._derivative_timesteps(adjointer, variable):
            functional_value = _add(functional_value,
                                    self._substitute_form(adjointer, timestep, dependencies, values))

        if functional_value is None:
            backend.info_red("Your functional is supposed to depend on %s, but does not?" % variable)
            raise libadjoint.exceptions.LibadjointErrorInvalidInputs

        d = backend.derivative(functional_value, values[dependencies.index(variable)].data)
        d = ufl.algorithms.expand_derivatives(d)

        if len(d.integrals()) == 0:
            raise SystemExit, "This isn't supposed to happen -- your functional is supposed to depend on %s" % variable
        return adjlinalg.Vector(d)
    def hessian(self, m_dot, project=False):
        ''' Evaluates the Hessian action in direction m_dot. '''

        assert(len(self.controls) == 1)

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls] + [m_dot])
            fnspaces = [p.data().function_space() if isinstance(p.data(),
                Function) else None for p in self.controls]

            if hash in self._cache["hessian_cache"]:
                info_green("Got a Hessian cache hit.")
                return cache_load(self._cache["hessian_cache"][hash], fnspaces)
            else:
                info_red("Got a Hessian cache miss")

        # Compute the Hessian action by solving the second order adjoint equations
        if isinstance(m_dot, list):
            assert len(m_dot) == 1
            Hm = self.H(m_dot[0], project=project)
        else:
            Hm = self.H(m_dot, project=project)

        # Apply the scaling factor
        scaled_Hm = [utils.scale(Hm, self.scale)]

        # Call callback
        control_data = [p.data() for p in self.controls]
        if self.current_func_value is not None:
            current_func_value = self.scale * self.current_func_value
        else:
            current_func_value = None

        self.hessian_cb(current_func_value,
                        delist(control_data, list_type=self.controls),
                        m_dot, scaled_Hm[0])

        # Cache the result
        if self.cache is not None:
            self._cache["hessian_cache"][hash] = cache_store(scaled_Hm, self.cache)

        return scaled_Hm
Example #27
0
    def __call__(self, adjointer, timestep, dependencies, values):

        functional_value = self._substitute_form(adjointer, timestep,
                                                 dependencies, values)

        if functional_value is not None:
            args = ufl.algorithms.extract_arguments(functional_value)
            if len(args) > 0:
                backend.info_red(
                    "The form passed into Functional must be rank-0 (a scalar)! You have passed in a rank-%s form."
                    % len(args))
                raise libadjoint.exceptions.LibadjointErrorInvalidInputs

            from .utils import _has_multimesh
            if _has_multimesh(functional_value):
                return backend.assemble_multimesh(functional_value)
            else:
                return backend.assemble(functional_value)
        else:
            return 0.0
    def derivative(self, forget=True, project=False):
        ''' Evaluates the derivative of the reduced functional for the most
        recently evaluated control value. '''

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls])
            fnspaces = [p.data().function_space() if isinstance(p.data(),
                Function) else None for p in self.controls]

            if hash in self._cache["derivative_cache"]:
                info_green("Got a derivative cache hit.")
                return cache_load(self._cache["derivative_cache"][hash], fnspaces)

        # Compute the gradient by solving the adjoint equations
        dfunc_value = drivers.compute_gradient(self.functional, self.controls, forget=forget, project=project)
        dfunc_value = enlist(dfunc_value)

        # Reset the checkpointing state in dolfin-adjoint
        adjointer.reset_revolve()

        # Apply the scaling factor
        scaled_dfunc_value = [utils.scale(df, self.scale) for df in list(dfunc_value)]

        # Call the user-specific callback routine
        if self.derivative_cb:
            if self.current_func_value is not None:
              values = [p.data() for p in self.controls]
              self.derivative_cb(self.scale * self.current_func_value,
                      delist(scaled_dfunc_value, list_type=self.controls),
                      delist(values, list_type=self.controls))
            else:
              info_red("Gradient evaluated without functional evaluation, not calling derivative callback function")

        # Cache the result
        if self.cache is not None:
            info_red("Got a derivative cache miss")
            self._cache["derivative_cache"][hash] = cache_store(scaled_dfunc_value, self.cache)

        return scaled_dfunc_value
Example #29
0
def replay_dolfin(forget=False, tol=0.0, stop=False):

    parameters["adjoint"]["stop_annotating"] = True
    if not backend.parameters["adjoint"]["record_all"]:
        info_red("Warning: your replay test will be much more effective with dolfin.parameters['adjoint']['record_all'] = True.")

    success = True
    for i in range(adjglobals.adjointer.equation_count):
        (fwd_var, output) = adjglobals.adjointer.get_forward_solution(i)
        storage = libadjoint.MemoryStorage(output)
        storage.set_compare(tol=tol)
        storage.set_overwrite(True)
        out = adjglobals.adjointer.record_variable(fwd_var, storage)
        success = success and out

        if forget:
            adjglobals.adjointer.forget_forward_equation(i)

        if not out and stop:
            return success

    return success
    def derivative(self, m_array=None, forget=True, project=False):
        ''' An implementation of the reduced functional derivative evaluation
            that accepts the controls as an array of scalars. If no control values are given,
            the result is derivative at the lastest forward run.
        '''

        # In the case that the control values have changed since the last forward run,
        # we first need to rerun the forward model with the new controls to have the
        # correct forward solutions
        m = [p.data() for p in self.controls]
        if m_array is not None and (m_array != self.get_global(m)).any():
            info_red("Rerunning forward model before computing derivative")
            self(m_array)

        dJdm = self.__base_derivative__(forget=forget, project=project)

        if project:
            dJdm_global = self.get_global(dJdm)
        else:
            dJdm_global = get_global(dJdm)

        return dJdm_global
    def derivative(self, m_array=None, forget=True, project=False):
        ''' An implementation of the reduced functional derivative evaluation
            that accepts the controls as an array of scalars. If no control values are given,
            the result is derivative at the lastest forward run.
        '''

        # In the case that the control values have changed since the last forward run,
        # we first need to rerun the forward model with the new controls to have the
        # correct forward solutions
        m = [p.data() for p in self.controls]
        if m_array is not None and (m_array != self.get_global(m)).any():
            info_red("Rerunning forward model before computing derivative")
            self(m_array)

        dJdm = self.__base_derivative__(forget=forget, project=project)

        if project:
            dJdm_global = self.get_global(dJdm)
        else:
            dJdm_global = get_global(dJdm)

        return dJdm_global
    def solve(self, var, b):
        timer = backend.Timer("Matrix-free solver")
        solver = backend.PETScKrylovSolver(*self.solver_parameters)
        solver.parameters.update(self.parameters)

        x = backend.Function(self.fn_space)
        if b.data is None:
            backend.info_red(
                "Warning: got zero RHS for the solve associated with variable %s"
                % var)
            return adjlinalg.Vector(x)

        if isinstance(b.data, backend.Function):
            rhs = b.data.vector().copy()
        else:
            rhs = backend.assemble(b.data)

        if var.type in ['ADJ_TLM', 'ADJ_ADJOINT']:
            self.bcs = [
                utils.homogenize(bc)
                for bc in self.bcs if isinstance(bc, backend.cpp.DirichletBC)
            ] + [
                bc
                for bc in self.bcs if not isinstance(bc, backend.DirichletBC)
            ]

        for bc in self.bcs:
            bc.apply(rhs)

        if self.operators[
                1] is not None:  # we have a user-supplied preconditioner
            solver.set_operators(self.data, self.operators[1])
            solver.solve(backend.down_cast(x.vector()), backend.down_cast(rhs))
        else:
            solver.solve(self.data, backend.down_cast(x.vector()),
                         backend.down_cast(rhs))

        timer.stop()
        return adjlinalg.Vector(x)
Example #33
0
def replay_dolfin(forget=False, tol=0.0, stop=False):

    with misc.annotations(False):
        if not backend.parameters["adjoint"]["record_all"]:
            info_red(
                "Warning: your replay test will be much more effective with dolfin.parameters['adjoint']['record_all'] = True."
            )

        success = True
        for i in range(adjglobals.adjointer.equation_count):
            (fwd_var, output) = adjglobals.adjointer.get_forward_solution(i)
            storage = libadjoint.MemoryStorage(output)
            storage.set_compare(tol=tol)
            storage.set_overwrite(True)
            out = adjglobals.adjointer.record_variable(fwd_var, storage)
            success = success and out

            if forget:
                adjglobals.adjointer.forget_forward_equation(i)

            if not out and stop:
                break

    return success
Example #34
0
    def derivative(self, adjointer, variable, dependencies, values):

        functional_value = None
        for timestep in self._derivative_timesteps(adjointer, variable):
            functional_value = _add(
                functional_value,
                self._substitute_form(adjointer, timestep, dependencies,
                                      values))

        if functional_value is None:
            backend.info_red(
                "Your functional is supposed to depend on %s, but does not?" %
                variable)
            raise libadjoint.exceptions.LibadjointErrorInvalidInputs

        d = backend.derivative(functional_value,
                               values[dependencies.index(variable)].data)
        d = ufl.algorithms.expand_derivatives(d)

        if len(d.integrals()) == 0:
            raise SystemExit(
                "This isn't supposed to happen -- your functional is supposed to depend on %s"
                % variable)
        return adjlinalg.Vector(d)
    def __call__(self, value):
        """ Evaluates the reduced functional for the given control value.

	Args:
	    value: The point in control space where to perform the Taylor test. Must be of the same type as the Control (e.g. Function, Constant or lists of latter).

	Returns:
	    float: The functional value.
        """

        # Make sure we do not annotate

        # Reset any cached data in dolfin-adjoint
        adj_reset_cache()

        #: The control values at which the reduced functional is to be evaluated.
        value = enlist(value)

        # Call callback
        self.eval_cb_pre(delist(value, list_type=self.controls))

        # Update the control values on the tape
        ListControl(self.controls).update(value)

        # Check if the result is already cached
        if self.cache:
            hash = value_hash(value)
            if hash in self._cache["functional_cache"]:
                # Found a cache
                info_green("Got a functional cache hit")
                return self._cache["functional_cache"][hash]

        # Replay the annotation and evaluate the functional
        func_value = 0.
        for i in range(adjointer.equation_count):
            (fwd_var, output) = adjointer.get_forward_solution(i)
            if isinstance(output.data, Function):
                output.data.rename(str(fwd_var),
                                   "a Function from dolfin-adjoint")

            # Call callback
            self.replay_cb(fwd_var, output.data,
                           delist(value, list_type=self.controls))

            # Check if we checkpointing is active and if yes
            # record the exact same checkpoint variables as
            # in the initial forward run
            if adjointer.get_checkpoint_strategy() != None:
                if str(fwd_var) in mem_checkpoints:
                    storage = libadjoint.MemoryStorage(output, cs=True)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)
                if str(fwd_var) in disk_checkpoints:
                    storage = libadjoint.MemoryStorage(output)
                    adjointer.record_variable(fwd_var, storage)
                    storage = libadjoint.DiskStorage(output, cs=True)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)
                if not str(fwd_var) in mem_checkpoints and not str(
                        fwd_var) in disk_checkpoints:
                    storage = libadjoint.MemoryStorage(output)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)

            # No checkpointing, so we record everything
            else:
                storage = libadjoint.MemoryStorage(output)
                storage.set_overwrite(True)
                adjointer.record_variable(fwd_var, storage)

            if i == adjointer.timestep_end_equation(fwd_var.timestep):
                func_value += adjointer.evaluate_functional(
                    self.functional, fwd_var.timestep)
                if adjointer.get_checkpoint_strategy() != None:
                    adjointer.forget_forward_equation(i)

        self.current_func_value = func_value

        # Call callback
        self.eval_cb_post(self.scale * func_value,
                          delist(value, list_type=self.controls))

        if self.cache:
            # Add result to cache
            info_red("Got a functional cache miss")
            self._cache["functional_cache"][hash] = self.scale * func_value

        return self.scale * func_value
Example #36
0
  def solve(self, *args, **kwargs):

    timer = backend.Timer("Matrix-free solver")

    annotate = True
    if "annotate" in kwargs:
      annotate = kwargs["annotate"]
      del kwargs["annotate"]

    if len(args) == 3:
      A = args[0]
      x = args[1]
      b = args[2]
    elif len(args) == 2:
      A = self.operators[0]
      x = args[0]
      b = args[1]

    if annotate:
      if not isinstance(A, AdjointKrylovMatrix):
        try:
          A = AdjointKrylovMatrix(A.form)
        except AttributeError:
          raise libadjoint.exceptions.LibadjointErrorInvalidInputs("Your A has to either be an AdjointKrylovMatrix or have been assembled after backend_adjoint was imported.")

      if not hasattr(x, 'function'):
        raise libadjoint.exceptions.LibadjointErrorInvalidInputs("Your x has to come from code like down_cast(my_function.vector()).")

      if not hasattr(b, 'form'):
        raise libadjoint.exceptions.LibadjointErrorInvalidInputs("Your b has to have the .form attribute: was it assembled with from backend_adjoint import *?")

      if not hasattr(A, 'dependencies'):
        backend.info_red("A has no .dependencies method; assuming no nonlinear dependencies of the matrix-free operator.")
        coeffs = []
        dependencies = []
      else:
        coeffs = [coeff for coeff in A.dependencies() if hasattr(coeff, 'function_space')]
        dependencies = [adjglobals.adj_variables[coeff] for coeff in coeffs]

      if len(dependencies) > 0:
        assert hasattr(A, "set_dependencies"), "Need a set_dependencies method to replace your values, if you have nonlinear dependencies ... "

      rhs = adjrhs.RHS(b.form)

      diag_name = hashlib.md5(str(hash(A)) + str(random.random())).hexdigest()
      diag_block = libadjoint.Block(diag_name, dependencies=dependencies, test_hermitian=backend.parameters["adjoint"]["test_hermitian"], test_derivative=backend.parameters["adjoint"]["test_derivative"])

      solving.register_initial_conditions(zip(rhs.coefficients(),rhs.dependencies()) + zip(coeffs, dependencies), linear=False, var=None)

      var = adjglobals.adj_variables.next(x.function)

      frozen_expressions_dict = expressions.freeze_dict()
      frozen_parameters = self.parameters.to_dict()

      def diag_assembly_cb(dependencies, values, hermitian, coefficient, context):
        '''This callback must conform to the libadjoint Python block assembly
        interface. It returns either the form or its transpose, depending on
        the value of the logical hermitian.'''

        assert coefficient == 1

        expressions.update_expressions(frozen_expressions_dict)

        if len(dependencies) > 0:
          A.set_dependencies(dependencies, [val.data for val in values])

        if hermitian:
          A_transpose = A.hermitian()
          return (MatrixFree(A_transpose, fn_space=x.function.function_space(), bcs=A_transpose.bcs,
                             solver_parameters=self.solver_parameters,
                             operators=transpose_operators(self.operators),
                             parameters=frozen_parameters), adjlinalg.Vector(None, fn_space=x.function.function_space()))
        else:
          return (MatrixFree(A, fn_space=x.function.function_space(), bcs=b.bcs,
                             solver_parameters=self.solver_parameters,
                             operators=self.operators,
                             parameters=frozen_parameters), adjlinalg.Vector(None, fn_space=x.function.function_space()))
      diag_block.assemble = diag_assembly_cb

      def diag_action_cb(dependencies, values, hermitian, coefficient, input, context):
        expressions.update_expressions(frozen_expressions_dict)
        A.set_dependencies(dependencies, [val.data for val in values])

        if hermitian:
          acting_mat = A.transpose()
        else:
          acting_mat = A

        output_fn = backend.Function(input.data.function_space())
        acting_mat.mult(input.data.vector(), output_fn.vector())
        vec = output_fn.vector()
        for i in range(len(vec)):
          vec[i] = coefficient * vec[i]

        return adjlinalg.Vector(output_fn)
      diag_block.action = diag_action_cb

      if len(dependencies) > 0:
        def derivative_action(dependencies, values, variable, contraction_vector, hermitian, input, coefficient, context):
          expressions.update_expressions(frozen_expressions_dict)
          A.set_dependencies(dependencies, [val.data for val in values])

          action = A.derivative_action(values[dependencies.index(variable)].data, contraction_vector.data, hermitian, input.data, coefficient)
          return adjlinalg.Vector(action)
        diag_block.derivative_action = derivative_action

      eqn = libadjoint.Equation(var, blocks=[diag_block], targets=[var], rhs=rhs)
      cs = adjglobals.adjointer.register_equation(eqn)
      solving.do_checkpoint(cs, var, rhs)

    out = backend.PETScKrylovSolver.solve(self, *args)

    if annotate:
      if backend.parameters["adjoint"]["record_all"]:
        adjglobals.adjointer.record_variable(var, libadjoint.MemoryStorage(adjlinalg.Vector(x.function)))

    timer.stop()

    return out
    def solve(self, *args, **kwargs):

        timer = backend.Timer("Matrix-free solver")

        annotate = True
        if "annotate" in kwargs:
            annotate = kwargs["annotate"]
            del kwargs["annotate"]

        if len(args) == 3:
            A = args[0]
            x = args[1]
            b = args[2]
        elif len(args) == 2:
            A = self.operators[0]
            x = args[0]
            b = args[1]

        if annotate:
            if not isinstance(A, AdjointKrylovMatrix):
                try:
                    A = AdjointKrylovMatrix(A.form)
                except AttributeError:
                    raise libadjoint.exceptions.LibadjointErrorInvalidInputs(
                        "Your A has to either be an AdjointKrylovMatrix or have been assembled after backend_adjoint was imported."
                    )

            if not hasattr(x, 'function'):
                raise libadjoint.exceptions.LibadjointErrorInvalidInputs(
                    "Your x has to come from code like down_cast(my_function.vector())."
                )

            if not hasattr(b, 'form'):
                raise libadjoint.exceptions.LibadjointErrorInvalidInputs(
                    "Your b has to have the .form attribute: was it assembled with from backend_adjoint import *?"
                )

            if not hasattr(A, 'dependencies'):
                backend.info_red(
                    "A has no .dependencies method; assuming no nonlinear dependencies of the matrix-free operator."
                )
                coeffs = []
                dependencies = []
            else:
                coeffs = [
                    coeff for coeff in A.dependencies()
                    if hasattr(coeff, 'function_space')
                ]
                dependencies = [
                    adjglobals.adj_variables[coeff] for coeff in coeffs
                ]

            if len(dependencies) > 0:
                assert hasattr(
                    A, "set_dependencies"
                ), "Need a set_dependencies method to replace your values, if you have nonlinear dependencies ... "

            rhs = adjrhs.RHS(b.form)

            key = '{}{}'.format(hash(A), random.random()).encode('utf8')
            diag_name = hashlib.md5(key).hexdigest()
            diag_block = libadjoint.Block(
                diag_name,
                dependencies=dependencies,
                test_hermitian=backend.parameters["adjoint"]["test_hermitian"],
                test_derivative=backend.parameters["adjoint"]
                ["test_derivative"])

            solving.register_initial_conditions(
                zip(rhs.coefficients(), rhs.dependencies()) +
                zip(coeffs, dependencies),
                linear=False,
                var=None)

            var = adjglobals.adj_variables.next(x.function)

            frozen_expressions_dict = expressions.freeze_dict()
            frozen_parameters = self.parameters.to_dict()

            def diag_assembly_cb(dependencies, values, hermitian, coefficient,
                                 context):
                '''This callback must conform to the libadjoint Python block assembly
                interface. It returns either the form or its transpose, depending on
                the value of the logical hermitian.'''

                assert coefficient == 1

                expressions.update_expressions(frozen_expressions_dict)

                if len(dependencies) > 0:
                    A.set_dependencies(dependencies,
                                       [val.data for val in values])

                if hermitian:
                    A_transpose = A.hermitian()
                    return (MatrixFree(
                        A_transpose,
                        fn_space=x.function.function_space(),
                        bcs=A_transpose.bcs,
                        solver_parameters=self.solver_parameters,
                        operators=transpose_operators(self.operators),
                        parameters=frozen_parameters),
                            adjlinalg.Vector(
                                None, fn_space=x.function.function_space()))
                else:
                    return (MatrixFree(
                        A,
                        fn_space=x.function.function_space(),
                        bcs=b.bcs,
                        solver_parameters=self.solver_parameters,
                        operators=self.operators,
                        parameters=frozen_parameters),
                            adjlinalg.Vector(
                                None, fn_space=x.function.function_space()))

            diag_block.assemble = diag_assembly_cb

            def diag_action_cb(dependencies, values, hermitian, coefficient,
                               input, context):
                expressions.update_expressions(frozen_expressions_dict)
                A.set_dependencies(dependencies, [val.data for val in values])

                if hermitian:
                    acting_mat = A.transpose()
                else:
                    acting_mat = A

                output_fn = backend.Function(input.data.function_space())
                acting_mat.mult(input.data.vector(), output_fn.vector())
                vec = output_fn.vector()
                for i in range(len(vec)):
                    vec[i] = coefficient * vec[i]

                return adjlinalg.Vector(output_fn)

            diag_block.action = diag_action_cb

            if len(dependencies) > 0:

                def derivative_action(dependencies, values, variable,
                                      contraction_vector, hermitian, input,
                                      coefficient, context):
                    expressions.update_expressions(frozen_expressions_dict)
                    A.set_dependencies(dependencies,
                                       [val.data for val in values])

                    action = A.derivative_action(
                        values[dependencies.index(variable)].data,
                        contraction_vector.data, hermitian, input.data,
                        coefficient)
                    return adjlinalg.Vector(action)

                diag_block.derivative_action = derivative_action

            eqn = libadjoint.Equation(var,
                                      blocks=[diag_block],
                                      targets=[var],
                                      rhs=rhs)
            cs = adjglobals.adjointer.register_equation(eqn)
            solving.do_checkpoint(cs, var, rhs)

        out = backend.PETScKrylovSolver.solve(self, *args)

        if annotate:
            if backend.parameters["adjoint"]["record_all"]:
                adjglobals.adjointer.record_variable(
                    var,
                    libadjoint.MemoryStorage(adjlinalg.Vector(x.function)))

        timer.stop()

        return out
    def __call__(self, value):
        """ Evaluates the reduced functional for the given control value.

	Args:
	    value: The point in control space where to perform the Taylor test. Must be of the same type as the Control (e.g. Function, Constant or lists of latter).

	Returns:
	    float: The functional value.
        """

        # Make sure we do not annotate

        # Reset any cached data in dolfin-adjoint
        adj_reset_cache()

        #: The control values at which the reduced functional is to be evaluated.
        value = enlist(value)

        # Call callback
        self.eval_cb_pre(delist(value, list_type=self.controls))

        # Update the control values on the tape
        ListControl(self.controls).update(value)

        # Check if the result is already cached
        if self.cache:
            hash = value_hash(value)
            if hash in self._cache["functional_cache"]:
                # Found a cache
                info_green("Got a functional cache hit")
                return self._cache["functional_cache"][hash]

        # Replay the annotation and evaluate the functional
        func_value = 0.
        for i in range(adjointer.equation_count):
            (fwd_var, output) = adjointer.get_forward_solution(i)
            if isinstance(output.data, Function):
                output.data.rename(str(fwd_var), "a Function from dolfin-adjoint")

            # Call callback
            self.replay_cb(fwd_var, output.data, delist(value, list_type=self.controls))

            # Check if we checkpointing is active and if yes
            # record the exact same checkpoint variables as
            # in the initial forward run
            if adjointer.get_checkpoint_strategy() != None:
                if str(fwd_var) in mem_checkpoints:
                    storage = libadjoint.MemoryStorage(output, cs = True)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)
                if str(fwd_var) in disk_checkpoints:
                    storage = libadjoint.MemoryStorage(output)
                    adjointer.record_variable(fwd_var, storage)
                    storage = libadjoint.DiskStorage(output, cs = True)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)
                if not str(fwd_var) in mem_checkpoints and not str(fwd_var) in disk_checkpoints:
                    storage = libadjoint.MemoryStorage(output)
                    storage.set_overwrite(True)
                    adjointer.record_variable(fwd_var, storage)

            # No checkpointing, so we record everything
            else:
                storage = libadjoint.MemoryStorage(output)
                storage.set_overwrite(True)
                adjointer.record_variable(fwd_var, storage)

            if i == adjointer.timestep_end_equation(fwd_var.timestep):
                func_value += adjointer.evaluate_functional(self.functional, fwd_var.timestep)
                if adjointer.get_checkpoint_strategy() != None:
                    adjointer.forget_forward_equation(i)

        self.current_func_value = func_value

        # Call callback
        self.eval_cb_post(self.scale * func_value, delist(value,
            list_type=self.controls))

        if self.cache:
            # Add result to cache
            info_red("Got a functional cache miss")
            self._cache["functional_cache"][hash] = self.scale*func_value

        return self.scale*func_value
    def derivative(self, forget=True, project=False):
        """ Evaluates the derivative of the reduced functional at the most
            recently evaluated control value.

	Args:
	    forget (Optional[bool]): Delete the forward state while solving the
                adjoint equations. If you want to reevaluate derivative at the same
                point (or the Hessian) you will need to set this to False or None. Defaults to True.
	    project (Optional[bool]): If True, the returned value will be the L2
                Riesz representer, if False it will be the l2 Riesz representative.
                The L2 projection requires one additional linear solve.
                Defaults to False.

	Returns:
	    The functional derivative. The returned type is the same as the control
            type.
        """

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls])
            fnspaces = [p.data().function_space() if isinstance(p.data(),
                Function) else None for p in self.controls]

            if hash in self._cache["derivative_cache"]:
                info_green("Got a derivative cache hit.")
                return cache_load(self._cache["derivative_cache"][hash], fnspaces)

        # Call callback
        values = [p.data() for p in self.controls]
        self.derivative_cb_pre(delist(values, list_type=self.controls))

        # Compute the gradient by solving the adjoint equations
        dfunc_value = drivers.compute_gradient(self.functional, self.controls, forget=forget, project=project)
        dfunc_value = enlist(dfunc_value)

        # Reset the checkpointing state in dolfin-adjoint
        adjointer.reset_revolve()

        # Apply the scaling factor
        scaled_dfunc_value = [utils.scale(df, self.scale) for df in list(dfunc_value)]

        # Call callback
        # We might have forgotten the control values already,
        # in which case we can only return Nones
        values = []
        for c in self.controls:
            try:
                values.append(p.data())
            except libadjoint.exceptions.LibadjointErrorNeedValue:
                values.append(None)
        if self.current_func_value is not None:
            self.derivative_cb_post(self.scale * self.current_func_value,
                    delist(scaled_dfunc_value, list_type=self.controls),
                    delist(values, list_type=self.controls))

        # Cache the result
        if self.cache is not None:
            info_red("Got a derivative cache miss")
            self._cache["derivative_cache"][hash] = cache_store(scaled_dfunc_value, self.cache)

        return scaled_dfunc_value
Example #40
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))
    def derivative(self, forget=True, project=False):
        """ Evaluates the derivative of the reduced functional at the most
            recently evaluated control value.

	Args:
	    forget (Optional[bool]): Delete the forward state while solving the
                adjoint equations. If you want to reevaluate derivative at the same
                point (or the Hessian) you will need to set this to False or None. Defaults to True.
	    project (Optional[bool]): If True, the returned value will be the L2
                Riesz representer, if False it will be the l2 Riesz representative.
                The L2 projection requires one additional linear solve.
                Defaults to False.

	Returns:
	    The functional derivative. The returned type is the same as the control
            type.
        """

        # Check if we have the gradient already in the cash.
        # If so, return the cached value
        if self.cache is not None:
            hash = value_hash([x.data() for x in self.controls])
            fnspaces = [
                p.data().function_space()
                if isinstance(p.data(), Function) else None
                for p in self.controls
            ]

            if hash in self._cache["derivative_cache"]:
                info_green("Got a derivative cache hit.")
                return cache_load(self._cache["derivative_cache"][hash],
                                  fnspaces)

        # Call callback
        values = [p.data() for p in self.controls]
        self.derivative_cb_pre(delist(values, list_type=self.controls))

        # Compute the gradient by solving the adjoint equations
        dfunc_value = drivers.compute_gradient(self.functional,
                                               self.controls,
                                               forget=forget,
                                               project=project)
        dfunc_value = enlist(dfunc_value)

        # Reset the checkpointing state in dolfin-adjoint
        adjointer.reset_revolve()

        # Apply the scaling factor
        scaled_dfunc_value = [
            utils.scale(df, self.scale) for df in list(dfunc_value)
        ]

        # Call callback
        # We might have forgotten the control values already,
        # in which case we can only return Nones
        values = []
        for p in self.controls:
            try:
                values.append(p.data())
            except libadjoint.exceptions.LibadjointErrorNeedValue:
                values.append(None)
        if self.current_func_value is not None:
            self.derivative_cb_post(
                self.scale * self.current_func_value,
                delist(scaled_dfunc_value, list_type=self.controls),
                delist(values, list_type=self.controls))

        # Cache the result
        if self.cache is not None:
            info_red("Got a derivative cache miss")
            self._cache["derivative_cache"][hash] = cache_store(
                scaled_dfunc_value, self.cache)

        return scaled_dfunc_value
Example #42
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))