def transpose_operators(operators): out = [None, None] for i in range(2): op = operators[i] if op is None: out[i] = None elif isinstance(op, backend.cpp.GenericMatrix): out[i] = op.__class__() backend.assemble(backend.adjoint(op.form), tensor=out[i]) if hasattr(op, 'bcs'): adjoint_bcs = [backend.homogenize(bc) for bc in op.bcs if isinstance(bc, backend.cpp.DirichletBC)] + [bc for bc in op.bcs if not isinstance(bc, backend.DirichletBC)] [bc.apply(out[i]) for bc in adjoint_bcs] elif isinstance(op, backend.Form) or isinstance(op, ufl.form.Form): out[i] = backend.adjoint(op) if hasattr(op, 'bcs'): out[i].bcs = [backend.homogenize(bc) for bc in op.bcs if isinstance(bc, backend.cpp.DirichletBC)] + [bc for bc in op.bcs if not isinstance(bc, backend.DirichletBC)] elif isinstance(op, AdjointKrylovMatrix): pass else: print "op.__class__: ", op.__class__ raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to transpose anything else!") return out
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
def _ad_convert_type(self, value, options=None): options = {} if options is None else options riesz_representation = options.get("riesz_representation", "l2") if riesz_representation == "l2": return create_overloaded_object( compat.function_from_vector(self.function_space(), value, cls=backend.Function)) elif riesz_representation == "L2": ret = compat.create_function(self.function_space()) u = backend.TrialFunction(self.function_space()) v = backend.TestFunction(self.function_space()) M = backend.assemble(backend.inner(u, v) * backend.dx) compat.linalg_solve(M, ret.vector(), value) return ret elif riesz_representation == "H1": ret = compat.create_function(self.function_space()) u = backend.TrialFunction(self.function_space()) v = backend.TestFunction(self.function_space()) M = backend.assemble( backend.inner(u, v) * backend.dx + backend.inner(backend.grad(u), backend.grad(v)) * backend.dx) compat.linalg_solve(M, ret.vector(), value) return ret elif callable(riesz_representation): return riesz_representation(value) else: raise NotImplementedError("Unknown Riesz representation %s" % riesz_representation)
def norm(self): if isinstance(self.data, backend.Function): return (abs(backend.assemble(backend.inner(self.data, self.data)*backend.dx)))**0.5 elif isinstance(self.data, ufl.form.Form): return backend.assemble(self.data).norm("l2") elif isinstance(self.data, backend.MultiMeshFunction): raise NotImplementedError
def dot_product(self,y): if isinstance(self.data, ufl.form.Form): return backend.assemble(backend.inner(self.data, y.data)*backend.dx) elif isinstance(self.data, backend.Function): if isinstance(y.data, ufl.form.Form): other = backend.assemble(y.data) else: other = y.data.vector() return self.data.vector().inner(other) else: raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to dot anything else.")
def _forward_solve(self, lhs, rhs, func, bcs, **kwargs): if self.assemble_system: A, b = backend.assemble_system(lhs, rhs, bcs) else: A = backend.assemble(lhs) b = backend.assemble(rhs) [bc.apply(A, b) for bc in bcs] if self.ident_zeros_tol is not None: A.ident_zeros(self.ident_zeros_tol) backend.solve(A, func.vector(), b, *self.forward_args, **self.forward_kwargs) return func
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
def _ad_dot(self, other, options=None): options = {} if options is None else options riesz_representation = options.get("riesz_representation", "l2") if riesz_representation == "l2": return self.vector().inner(other.vector()) elif riesz_representation == "L2": return backend.assemble(backend.inner(self, other) * backend.dx) elif riesz_representation == "H1": return backend.assemble( (backend.inner(self, other) + backend.inner(backend.grad(self), backend.grad(other))) * backend.dx) else: raise NotImplementedError("Unknown Riesz representation %s" % riesz_representation)
def action(self, x, y): assert isinstance(x.data, backend.Function) assert isinstance(y.data, backend.Function) action_form = backend.action(self.data, x.data) action_vec = backend.assemble(action_form) y.data.vector()[:] = action_vec
def equation_partial_derivative(self, adjointer, adjoint, i, variable): form = adjresidual.get_residual(i) if form is None: return None else: form = -form fn_space = ufl.algorithms.extract_arguments(form)[0].function_space() dparam = backend.Function( backend.FunctionSpace(fn_space.mesh(), "R", 0)) dparam.vector()[:] = 1.0 dJdv = numpy.zeros(len(self.v)) for (i, a) in enumerate(self.v): diff_form = ufl.algorithms.expand_derivatives( backend.derivative(form, a, dparam)) dFdm = backend.assemble(diff_form) # actually - dF/dm assert isinstance(dFdm, backend.GenericVector) out = dFdm.inner(adjoint.vector()) dJdv[i] = out return dJdv
def equation_partial_second_derivative(self, adjointer, adjoint, i, variable, m_dot): form = adjresidual.get_residual(i) if form is not None: form = -form mesh = ufl.algorithms.extract_arguments( form)[0].function_space().mesh() fn_space = backend.FunctionSpace(mesh, "R", 0) dparam = backend.Function(fn_space) dparam.vector()[:] = 1.0 * float(self.coeff) d2param = backend.Function(fn_space) d2param.vector()[:] = 1.0 * float(self.coeff) * m_dot diff_form = ufl.algorithms.expand_derivatives( backend.derivative(form, get_constant(self.a), dparam)) if diff_form is None: return None diff_form = ufl.algorithms.expand_derivatives( backend.derivative(diff_form, get_constant(self.a), d2param)) if diff_form is None: return None # Let's see if the form actually depends on the parameter m if len(diff_form.integrals()) != 0: dFdm = backend.assemble(diff_form) # actually - dF/dm assert isinstance(dFdm, backend.GenericVector) out = dFdm.inner(adjoint.vector()) return out else: return None # dF/dm is zero, return None
def inner(self, vec): '''Compute the action of the gradient on the vector vec.''' def make_mdot(vec): if isinstance(self.m, FunctionControl): mdot = self.m.set_perturbation( backend.Function(self.m.data().function_space(), vec)) elif isinstance(self.m, ConstantControl): mdot = self.m.set_perturbation(backend.Constant(vec)) return mdot mdot = make_mdot(vec) grad = 0.0 last_timestep = -1 for (tlm, tlm_var) in compute_tlm(mdot, forget=self.forget): self.cb(tlm_var, tlm) fwd_var = tlm_var.to_forward() dJdu = adjglobals.adjointer.evaluate_functional_derivative( self.J, fwd_var) if dJdu is not None and tlm is not None: dJdu_vec = backend.assemble(dJdu.data) grad = _add(grad, dJdu_vec.inner(tlm.vector())) if last_timestep < tlm_var.timestep: out = self.m.functional_partial_derivative( adjglobals.adjointer, self.J, tlm_var.timestep) grad = _add(grad, out) last_timestep = tlm_var.timestep return grad
def functional_partial_derivative(self, adjointer, J, timestep): form = J.get_form(adjointer, timestep) if form is None: return None # OK. Now that we have the form for the functional at this timestep, let's differentiate it with respect to # my dear Constant, and be done. for coeff in ufl.algorithms.extract_coefficients(form): try: mesh = coeff.function_space().mesh() fn_space = backend.FunctionSpace(mesh, "R", 0) break except: pass dparam = backend.Function(fn_space) dparam.vector()[:] = 1.0 * float(self.coeff) d = backend.derivative(form, get_constant(self.a), dparam) d = ufl.algorithms.expand_derivatives(d) # Add the derivatives of Expressions wrt to the Constant d = self.expression_derivative(form, d) if len(d.integrals()) != 0: return backend.assemble(d) else: return None
def assemble(*args, **kwargs): """When a form is assembled, the information about its nonlinear dependencies is lost, and it is no longer easy to manipulate. Therefore, fenics_adjoint overloads the :py:func:`dolfin.assemble` function to *attach the form to the assembled object*. This lets the automatic annotation work, even when the user calls the lower-level :py:data:`solve(A, x, b)`. """ annotate = annotate_tape(kwargs) with stop_annotating(): output = backend.assemble(*args, **kwargs) form = args[0] if isinstance(output, float): output = create_overloaded_object(output) if annotate: block = AssembleBlock(form) tape = get_working_tape() tape.add_block(block) block.add_output(output.block_variable) else: # Assembled a vector or matrix output.form = form return output
def inner(self, vec): '''Compute the action of the gradient on the vector vec.''' def make_mdot(vec): if isinstance(self.m, FunctionControl): mdot = self.m.set_perturbation(backend.Function(self.m.data().function_space(), vec)) elif isinstance(self.m, ConstantControl): mdot = self.m.set_perturbation(backend.Constant(vec)) return mdot mdot = make_mdot(vec) grad = 0.0 last_timestep = -1 for (tlm, tlm_var) in compute_tlm(mdot, forget=self.forget): self.cb(tlm_var, tlm) fwd_var = tlm_var.to_forward() dJdu = adjglobals.adjointer.evaluate_functional_derivative(self.J, fwd_var) if dJdu is not None and tlm is not None: dJdu_vec = backend.assemble(dJdu.data) grad = _add(grad, dJdu_vec.inner(tlm.vector())) if last_timestep < tlm_var.timestep: out = self.m.functional_partial_derivative(adjglobals.adjointer, self.J, tlm_var.timestep) grad = _add(grad, out) last_timestep = tlm_var.timestep return grad
def equation_partial_second_derivative(self, adjointer, adjoint, i, variable, m_dot): form = adjresidual.get_residual(i) if form is not None: form = -form mesh = ufl.algorithms.extract_arguments(form)[0].function_space().mesh() fn_space = backend.FunctionSpace(mesh, "R", 0) dparam = backend.Function(fn_space) dparam.vector()[:] = 1.0 * float(self.coeff) d2param = backend.Function(fn_space) d2param.vector()[:] = 1.0 * float(self.coeff) * m_dot diff_form = ufl.algorithms.expand_derivatives(backend.derivative(form, get_constant(self.a), dparam)) if diff_form is None: return None diff_form = ufl.algorithms.expand_derivatives(backend.derivative(diff_form, get_constant(self.a), d2param)) if diff_form is None: return None # Let's see if the form actually depends on the parameter m if len(diff_form.integrals()) != 0: dFdm = backend.assemble(diff_form) # actually - dF/dm assert isinstance(dFdm, backend.GenericVector) out = dFdm.inner(adjoint.vector()) return out else: return None # dF/dm is zero, return None
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)
def functional_partial_second_derivative(self, adjointer, J, timestep, m_dot): form = J.get_form(adjointer, timestep) if form is None: return None for coeff in ufl.algorithms.extract_coefficients(form): try: mesh = coeff.function_space().mesh() fn_space = backend.FunctionSpace(mesh, "R", 0) break except: pass dparam = backend.Function(fn_space) dparam.vector()[:] = 1.0 * float(self.coeff) d = backend.derivative(form, get_constant(self.a), dparam) d = ufl.algorithms.expand_derivatives(d) d2param = backend.Function(fn_space) d2param.vector()[:] = 1.0 * float(self.coeff) * m_dot d = backend.derivative(d, get_constant(self.a), d2param) d = ufl.algorithms.expand_derivatives(d) if len(d.integrals()) != 0: return backend.assemble(d) else: return None
def equation_partial_derivative(self, adjointer, adjoint, i, variable): form = adjresidual.get_residual(i) if form is not None: form = -form mesh = ufl.algorithms.extract_arguments( form)[0].function_space().mesh() fn_space = backend.FunctionSpace(mesh, "R", 0) dparam = backend.Function(fn_space) dparam.vector()[:] = 1.0 * float(self.coeff) diff_form = ufl.algorithms.expand_derivatives( backend.derivative(form, get_constant(self.a), dparam)) # Add the derivatives of Expressions wrt to the Constant diff_form = self.expression_derivative(form, diff_form) # Let's see if the form actually depends on the parameter m if len(diff_form.integrals()) != 0: dFdm = backend.assemble(diff_form) # actually - dF/dm out = adjoint.vector().inner(dFdm) else: out = None # dF/dm is zero, return None return out
def _assemble_and_solve_adj_eq(self, dFdu_adj_form, dJdu, compute_bdy): dJdu_copy = dJdu.copy() bcs = self._homogenize_bcs() if self.assemble_system: rhs_bcs_form = backend.inner( backend.Function(self.function_space), dFdu_adj_form.arguments()[0]) * backend.dx A, _ = backend.assemble_system(dFdu_adj_form, rhs_bcs_form, bcs) else: A = backend.assemble(dFdu_adj_form) [bc.apply(A) for bc in bcs] [bc.apply(dJdu) for bc in bcs] adj_sol = compat.create_function(self.function_space) compat.linalg_solve(A, adj_sol.vector(), dJdu, *self.adj_args, **self.adj_kwargs) adj_sol_bdy = None if compute_bdy: adj_sol_bdy = compat.function_from_vector( self.function_space, dJdu_copy - compat.assemble_adjoint_value( backend.action(dFdu_adj_form, adj_sol))) return adj_sol, adj_sol_bdy
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 jacobian(self, m): if isinstance(m, list): assert len(m) == 1 m = m[0] self.update_control(m) out = [backend.assemble(self.dform)] return out
def mult(self, *args): shapes = self.shape(self.current_form) y = backend.PETScVector(shapes[0]) action_fn = backend.Function(ufl.algorithms.extract_arguments(self.current_form)[-1].function_space()) action_vec = action_fn.vector() for i in range(len(args[0])): action_vec[i] = args[0][i] action_form = backend.action(self.current_form, action_fn) backend.assemble(action_form, tensor=y) for bc in self.bcs: bcvals = bc.get_boundary_values() for idx in bcvals: y[idx] = action_vec[idx] args[1].set_local(y.array())
def taylor_test_expression(exp, V, seed=None): """ Performs a Taylor test of an Expression with dependencies. exp: The expression to test V: A suitable function space on which the expression will be projected. seed: The initial perturbation coefficient for the taylor test. Warning: This function resets the adjoint tape! """ from . import drivers, functional, projection from .controls import Control adjglobals.adj_reset() # Annotate test model s = projection.project(exp, V, annotate=True) mesh = V.mesh() Jform = s**2*backend.dx + exp*backend.dx(domain=mesh) J = functional.Functional(Jform) J0 = backend.assemble(Jform) controls = [Control(c) for c in exp.dependencies] dJd0 = drivers.compute_gradient(J, controls, forget=False) for i in range(len(controls)): def Jfunc(new_val): dep = exp.dependencies[i] # Remember the old dependency value for later old_val = float(dep) # Compute the functional value dep.assign(new_val) s = projection.project(exp, V, annotate=False) out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh)) # Restore the old dependency value dep.assign(old_val) return out #HJ = hessian(J, controls[i], warn=False) #minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], HJm=HJ) minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], seed=seed) if math.isnan(minconv): warning("Convergence order is not a number. Assuming that you \ have a linear or constant constraint dependency (e.g. check that the Taylor \ remainder are all 0).") else: if not minconv > 1.9: raise Exception("The Taylor test failed when checking the \ derivative with respect to the %i'th dependency." % (i+1)) adjglobals.adj_reset()
def basic_solve(self, var, b): if isinstance(self.data, IdentityMatrix): x=b.duplicate() x.axpy(1.0, b) if isinstance(x.data, ufl.Form): x = Vector(backend.Function(x.fn_space, backend.assemble(x.data))) else: if var.type in ['ADJ_TLM', 'ADJ_ADJOINT', 'ADJ_SOA']: dirichlet_bcs = [utils.homogenize(bc) for bc in self.bcs if isinstance(bc, backend.DirichletBC)] other_bcs = [bc for bc in self.bcs if not isinstance(bc, backend.DirichletBC)] bcs = dirichlet_bcs + other_bcs else: bcs = self.bcs test = self.test_function() #FIXME: This is a hack, the test and trial function should carry # the MultiMeshFunctionSpace as an attribute if hasattr(test, '_V_multi'): x = Vector(backend.MultiMeshFunction(test._V_multi)) else: x = Vector(backend.Function(test.function_space())) #print "b.data is a %s in the solution of %s" % (b.data.__class__, var) if b.data is None and not hasattr(b, 'nonlinear_form'): # This means we didn't get any contribution on the RHS of the adjoint system. This could be that the # simulation ran further ahead than when the functional was evaluated, or it could be that the # functional is set up incorrectly. backend.warning("Warning: got zero RHS for the solve associated with variable %s" % var) elif isinstance(b.data, backend.Function): assembled_lhs = self.assemble_data() [bc.apply(assembled_lhs) for bc in bcs] assembled_rhs = compatibility.assembled_rhs(b) [bc.apply(assembled_rhs) for bc in bcs] wrap_solve(assembled_lhs, x.data, assembled_rhs, self.solver_parameters) else: if hasattr(b, 'nonlinear_form'): # was a nonlinear solve x = compatibility.assign_function_to_vector(x, b.nonlinear_u, function_space = test.function_space()) F = backend.replace(b.nonlinear_form, {b.nonlinear_u: x.data}) J = backend.replace(b.nonlinear_J, {b.nonlinear_u: x.data}) try: compatibility.solve(F == 0, x.data, b.nonlinear_bcs, J=J, solver_parameters=self.solver_parameters) except RuntimeError as rte: x.data.vector()[:] = float("nan") else: assembled_lhs = self.assemble_data() [bc.apply(assembled_lhs) for bc in bcs] assembled_rhs = wrap_assemble(b.data, test) [bc.apply(assembled_rhs) for bc in bcs] wrap_solve(assembled_lhs, x.data, assembled_rhs, self.solver_parameters) return x
def transpose_operators(operators): out = [None, None] for i in range(2): op = operators[i] if op is None: out[i] = None elif isinstance(op, backend.cpp.GenericMatrix): out[i] = op.__class__() backend.assemble(backend.adjoint(op.form), tensor=out[i]) if hasattr(op, 'bcs'): adjoint_bcs = [ utils.homogenize(bc) for bc in op.bcs if isinstance(bc, backend.cpp.DirichletBC) ] + [ bc for bc in op.bcs if not isinstance(bc, backend.DirichletBC) ] [bc.apply(out[i]) for bc in adjoint_bcs] elif isinstance(op, backend.Form) or isinstance(op, ufl.form.Form): out[i] = backend.adjoint(op) if hasattr(op, 'bcs'): out[i].bcs = [ utils.homogenize(bc) for bc in op.bcs if isinstance(bc, backend.cpp.DirichletBC) ] + [ bc for bc in op.bcs if not isinstance(bc, backend.DirichletBC) ] elif isinstance(op, AdjointKrylovMatrix): pass else: print("op.__class__: ", op.__class__) raise libadjoint.exceptions.LibadjointErrorNotImplemented( "Don't know how to transpose anything else!") return out
def jacobian_action(self, m, dm, result): """Computes the Jacobian action of c(m) in direction dm and stores the result in result. """ if isinstance(m, list): assert len(m) == 1 m = m[0] self.update_control(m) form = backend.action(self.dform, dm) result.assign(backend.assemble(form))
def taylor_test_expression(exp, V): """ Performs a Taylor test of an Expression with dependencies. exp: The expression to test V: A suitable function space on which the expression will be projected. Warning: This function resets the adjoint tape! """ adjglobals.adj_reset() # Annotate test model s = projection.project(exp, V, annotate=True) mesh = V.mesh() Jform = s**2*backend.dx + exp*backend.dx(domain=mesh) J = functional.Functional(Jform) J0 = backend.assemble(Jform) deps = exp.dependencies() controls = [Control(c) for c in deps] dJd0 = drivers.compute_gradient(J, controls, forget=False) for i in range(len(controls)): def Jfunc(new_val): dep = exp.dependencies()[i] # Remember the old dependency value for later old_val = float(dep) # Compute the functional value dep.assign(new_val) s = projection.project(exp, V, annotate=False) out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh)) # Restore the old dependency value dep.assign(old_val) return out #HJ = hessian(J, controls[i], warn=False) #minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i], HJm=HJ) minconv = taylor_test(Jfunc, controls[i], J0, dJd0[i]) if math.isnan(minconv): warning("Convergence order is not a number. Assuming that you \ have a linear or constant constraint dependency (e.g. check that the Taylor \ remainder are all 0).") else: if not minconv > 1.9: raise Exception, "The Taylor test failed when checking the \ derivative with respect to the %i'th dependency." % (i+1) adjglobals.adj_reset()
def project_test(func): if isinstance(func, backend.Function): V = func.function_space() u = backend.TrialFunction(V) v = backend.TestFunction(V) M = backend.assemble(backend.inner(u, v)*backend.dx) proj = backend.Function(V) backend.solve(M, proj.vector(), func.vector()) return proj else: return func
def size(self): if hasattr(self, "fn_space") and self.data is None: self.data = backend.Function(self.fn_space) if isinstance(self.data, backend.Function): return self.data.vector().local_size() if isinstance(self.data, ufl.form.Form): return backend.assemble(self.data).local_size() raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to get the size.")
def project_test(func): if isinstance(func, backend.Function): V = func.function_space() u = backend.TrialFunction(V) v = backend.TestFunction(V) M = backend.assemble(backend.inner(u, v) * backend.dx) proj = backend.Function(V) backend.solve(M, proj.vector(), func.vector()) return proj else: return func
def mult(self, *args): shapes = self.shape(self.current_form) y = backend.PETScVector(shapes[0]) action_fn = backend.Function( ufl.algorithms.extract_arguments( self.current_form)[-1].function_space()) action_vec = action_fn.vector() for i in range(len(args[0])): action_vec[i] = args[0][i] action_form = backend.action(self.current_form, action_fn) backend.assemble(action_form, tensor=y) for bc in self.bcs: bcvals = bc.get_boundary_values() for idx in bcvals: y[idx] = action_vec[idx] args[1].set_local(y.array())
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
def hessian_action(self, m, dm, dp, result): """Computes the Hessian action of c(m) in direction dm and dp and stores the result in result. """ if isinstance(m, list): assert len(m) == 1 m = m[0] self.update_control(m) H = dm * ufl.replace(self.hess, {self.trial: dp}) if isinstance(result, backend.Function): if backend.__name__ in ["dolfin", "fenics"]: if self.zero_hess: result.vector().zero() else: result.vector().zero() result.vector().axpy(1.0, backend.assemble(H)) else: if self.zero_hess: result.assign(0) else: result.assign(backend.assemble(H)) else: raise NotImplementedError("Do I need to untangle all controls?")
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 Jfunc(new_val): dep = exp.dependencies()[i] # Remember the old dependency value for later old_val = float(dep) # Compute the functional value dep.assign(new_val) s = projection.project(exp, V, annotate=False) out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh)) # Restore the old dependency value dep.assign(old_val) return out
def Jfunc(new_val): dep = exp.dependencies[i] # Remember the old dependency value for later old_val = float(dep) # Compute the functional value dep.assign(new_val) s = projection.project(exp, V, annotate=False) out = backend.assemble(s**2*backend.dx + exp*backend.dx(domain=mesh)) # Restore the old dependency value dep.assign(old_val) return out
def __call__(self, adjointer, timestep, dependencies, values): if self.verbose: print("eval ", len(values)) print("timestep ", timestep) print("\r\n******************") toi = _time_levels(adjointer, timestep)[0] # time of interest my = [0.0] * len(self.coords) for i in range(len(self.coords)): if not self.skip[i] and len(values) > 0: if timestep is adjointer.timestep_count - 1: # add final contribution if self.index[i] is None: solu = values[0].data(self.coords[i]) else: solu = values[0].data(self.coords[i])[self.index[i]] ref = self.refs[i][self.times.index(self.times[-1])] my[i] = (solu - float(ref)) * (solu - float(ref)) # if necessary, add one but last contribution if toi in self.times and len(values) > 0: if self.index[i] is None: solu = values[-1].data(self.coords[i]) else: solu = values[-1].data( self.coords[i])[self.index[i]] ref = self.refs[i][self.times.index(toi)] my[i] += (solu - float(ref)) * (solu - float(ref)) elif timestep is 0: return backend.assemble(self.regform) else: # normal situation if self.index[i] is None: solu = values[-1].data(self.coords[i]) else: solu = values[-1].data(self.coords[i])[self.index[i]] ref = self.refs[i][self.times.index(toi)] my[i] = (solu - float(ref)) * (solu - float(ref)) if self.verbose: print("my eval ", my[i]) print("eval ", timestep, " times ", _time_levels(adjointer, timestep)) return self.alpha * sum(my)
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)
def jacobian_adjoint_action(self, m, dp, result): """Computes the Jacobian adjoint action of c(m) in direction dp and stores the result in result. """ if isinstance(m, list): assert len(m) == 1 m = m[0] self.update_control(m) asm = backend.assemble( dp * ufl.replace(self.dform, {self.trial: self.test})) if isinstance(result, backend.Function): if backend.__name__ in ["dolfin", "fenics"]: result.vector().zero() result.vector().axpy(1.0, asm) else: result.assign(asm) else: raise NotImplementedError("Do I need to untangle all controls?")
def wrap_assemble(form, test): '''If you do F = inner(grad(TrialFunction(V), grad(TestFunction(V)))) a = lhs(F); L = rhs(F) solve(a == L, ...) it works, even though L is empty. But if you try to assemble(L) as we do here, you get a crash. This function wraps assemble to catch that crash and return an empty RHS instead. ''' try: b = backend.assemble(form) except RuntimeError: assert len(form.integrals()) == 0 b = backend.Function(test.function_space()).vector() return b
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 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)
def equation_partial_derivative(self, adjointer, adjoint, i, variable): form = adjresidual.get_residual(i) if form is None: return None else: form = -form fn_space = ufl.algorithms.extract_arguments(form)[0].function_space() dparam = backend.Function(backend.FunctionSpace(fn_space.mesh(), "R", 0)) dparam.vector()[:] = 1.0 dJdv = numpy.zeros(len(self.v)) for (i, a) in enumerate(self.v): diff_form = ufl.algorithms.expand_derivatives(backend.derivative(form, a, dparam)) dFdm = backend.assemble(diff_form) # actually - dF/dm assert isinstance(dFdm, backend.GenericVector) out = dFdm.inner(adjoint.vector()) dJdv[i] = out return dJdv
def assemble(self): u = TrialFunction(self.V) v = TestFunction(self.V) A = inner(u, v)*dx return assemble(A)
def assemble(self): u = TrialFunction(self.V) v = TestFunction(self.V) A = inner(u, v)*dx + alpha*inner(grad(u), grad(v))*dx return assemble(A)
def norm(self): if isinstance(self.data, backend.Function): return (abs(backend.assemble(backend.inner(self.data, self.data)*backend.dx)))**0.5 elif isinstance(self.data, ufl.form.Form): return backend.assemble(self.data).norm("l2")
def perturbed_replay(parameter, perturbation, perturbation_scale, observation, perturbation_norm="mass", observation_norm="mass", callback=None, forget=False): r"""Perturb the forward run and compute .. math:: \frac{ \left|\left| \delta \mathrm{observation} \right|\right| }{ \left|\left| \delta \mathrm{input} \right| \right| } as a function of time. :py:data:`parameter` -- an FunctionControl to say what variable should be perturbed (e.g. FunctionControl('InitialConcentration')) :py:data:`perturbation` -- a Function to give the perturbation direction (from a GST analysis, for example) :py:data:`perturbation_norm` -- a bilinear Form which induces a norm on the space of perturbation inputs :py:data:`perturbation_scale` -- how big the norm of the initial perturbation should be :py:data:`observation` -- the variable to observe (e.g. 'Concentration') :py:data:`observation_norm` -- a bilinear Form which induces a norm on the space of perturbation outputs :py:data:`callback` -- a function f(var, perturbed, unperturbed) that the user can supply (e.g. to dump out variables during the perturbed replay) """ if not backend.parameters["adjoint"]["record_all"]: info_red("Warning: your replay test will be much more effective with backend.parameters['adjoint']['record_all'] = True.") assert isinstance(parameter, controls.FunctionControl) if perturbation_norm == "mass": p_fnsp = perturbation.function_space() u = backend.TrialFunction(p_fnsp) v = backend.TestFunction(p_fnsp) p_mass = backend.inner(u, v)*backend.dx perturbation_norm = p_mass if not isinstance(perturbation_norm, backend.GenericMatrix): perturbation_norm = backend.assemble(perturbation_norm) if not isinstance(observation_norm, backend.GenericMatrix) and observation_norm != "mass": observation_norm = backend.assemble(observation_norm) def compute_norm(perturbation, norm): # Need to compute <x, Ax> and then take its sqrt # where x is perturbation, A is norm try: vec = perturbation.vector() except: vec = perturbation Ax = vec.copy() norm.mult(vec, Ax) xAx = vec.inner(Ax) return math.sqrt(xAx) growths = [] for i in range(adjglobals.adjointer.equation_count): (fwd_var, output) = adjglobals.adjointer.get_forward_solution(i) if fwd_var == parameter.var: # we've hit the initial condition we want to perturb current_norm = compute_norm(perturbation, perturbation_norm) output.data.vector()[:] += (perturbation_scale/current_norm) * perturbation.vector() unperturbed = adjglobals.adjointer.get_variable_value(fwd_var).data if fwd_var.name == observation: # we've hit something we want to observe # Fetch the unperturbed result from the record if observation_norm == "mass": # we can't do this earlier, because we don't have the observation function space yet o_fnsp = output.data.function_space() u = backend.TrialFunction(o_fnsp) v = backend.TestFunction(o_fnsp) o_mass = backend.inner(u, v)*backend.dx observation_norm = backend.assemble(o_mass) diff = output.data.vector() - unperturbed.vector() growths.append(compute_norm(diff, observation_norm)/perturbation_scale) # <--- the action line if callback is not None: callback(fwd_var, output.data, unperturbed) storage = libadjoint.MemoryStorage(output) storage.set_compare(tol=None) storage.set_overwrite(True) out = adjglobals.adjointer.record_variable(fwd_var, storage) if forget: adjglobals.adjointer.forget_forward_equation(i) # can happen if we initialised a nonlinear solve with a constant zero guess if growths[0] == 0.0: return growths[1:] else: return growths
def axpy(self, alpha, x): if hasattr(x, 'nonlinear_form'): self.nonlinear_form = x.nonlinear_form self.nonlinear_u = x.nonlinear_u self.nonlinear_bcs = x.nonlinear_bcs self.nonlinear_J = x.nonlinear_J if x.zero: return if (self.data is None): # self is an empty form. if isinstance(x.data, backend.Function): self.data = backend.Function(x.data) self.data.vector()._scale(alpha) else: self.data=alpha*x.data elif x.data is None: pass elif isinstance(self.data, backend.Coefficient): if isinstance(x.data, backend.Coefficient): self.data.vector().axpy(alpha, x.data.vector()) else: # This occurs when adding a RHS derivative to an adjoint equation # corresponding to the initial conditions. self.data.vector().axpy(alpha, backend.assemble(x.data)) self.data.form = alpha * x.data elif isinstance(x.data, ufl.form.Form) and isinstance(self.data, ufl.form.Form): # Let's do a bit of argument shuffling, shall we? xargs = ufl.algorithms.extract_arguments(x.data) sargs = ufl.algorithms.extract_arguments(self.data) if xargs != sargs: # OK, let's check that all of the function spaces are happy and so on. for i in range(len(xargs)): assert xargs[i].element() == sargs[i].element() assert xargs[i].function_space() == sargs[i].function_space() # Now that we are happy, let's replace the xargs with the sargs ones. x_form = backend.replace(x.data, dict(zip(xargs, sargs))) else: x_form = x.data self.data+=alpha*x_form elif isinstance(self.data, ufl.form.Form) and isinstance(x.data, backend.Function): #print "axpy assembling FormFunc. self.data is a %s; x.data is a %s" % (self.data.__class__, x.data.__class__) x_vec = x.data.vector().copy() self_vec = backend.assemble(self.data) self_vec.axpy(alpha, x_vec) new_fn = backend.Function(x.data.function_space()) new_fn.vector()[:] = self_vec self.data = new_fn self.fn_space = self.data.function_space() else: print "self.data.__class__: ", self.data.__class__ print "x.data.__class__: ", x.data.__class__ assert False self.zero = False
def assemble(self): u = TrialFunction(self.V) v = TestFunction(self.V) A = inner(u, v) * dx return assemble(A)
def output_workspace(self): """Return an object like the output of c(m) for calculations.""" return backend_types.Constant(backend.assemble(self.form))
def function(self, m): self.update_control(m) b = backend.assemble(self.form) return backend_types.Constant(b)
def perturbed_replay(parameter, perturbation, perturbation_scale, observation, perturbation_norm="mass", observation_norm="mass", callback=None, forget=False): r"""Perturb the forward run and compute .. math:: \frac{ \left|\left| \delta \mathrm{observation} \right|\right| }{ \left|\left| \delta \mathrm{input} \right| \right| } as a function of time. :py:data:`parameter` -- an FunctionControl to say what variable should be perturbed (e.g. FunctionControl('InitialConcentration')) :py:data:`perturbation` -- a Function to give the perturbation direction (from a GST analysis, for example) :py:data:`perturbation_norm` -- a bilinear Form which induces a norm on the space of perturbation inputs :py:data:`perturbation_scale` -- how big the norm of the initial perturbation should be :py:data:`observation` -- the variable to observe (e.g. 'Concentration') :py:data:`observation_norm` -- a bilinear Form which induces a norm on the space of perturbation outputs :py:data:`callback` -- a function f(var, perturbed, unperturbed) that the user can supply (e.g. to dump out variables during the perturbed replay) """ if not backend.parameters["adjoint"]["record_all"]: info_red( "Warning: your replay test will be much more effective with backend.parameters['adjoint']['record_all'] = True." ) assert isinstance(parameter, controls.FunctionControl) if perturbation_norm == "mass": p_fnsp = perturbation.function_space() u = backend.TrialFunction(p_fnsp) v = backend.TestFunction(p_fnsp) p_mass = backend.inner(u, v) * backend.dx perturbation_norm = p_mass if not isinstance(perturbation_norm, backend.GenericMatrix): perturbation_norm = backend.assemble(perturbation_norm) if not isinstance(observation_norm, backend.GenericMatrix) and observation_norm != "mass": observation_norm = backend.assemble(observation_norm) def compute_norm(perturbation, norm): # Need to compute <x, Ax> and then take its sqrt # where x is perturbation, A is norm try: vec = perturbation.vector() except: vec = perturbation Ax = vec.copy() norm.mult(vec, Ax) xAx = vec.inner(Ax) return math.sqrt(xAx) growths = [] for i in range(adjglobals.adjointer.equation_count): (fwd_var, output) = adjglobals.adjointer.get_forward_solution(i) if fwd_var == parameter.var: # we've hit the initial condition we want to perturb current_norm = compute_norm(perturbation, perturbation_norm) output.data.vector()[:] += (perturbation_scale / current_norm) * perturbation.vector() unperturbed = adjglobals.adjointer.get_variable_value(fwd_var).data if fwd_var.name == observation: # we've hit something we want to observe # Fetch the unperturbed result from the record if observation_norm == "mass": # we can't do this earlier, because we don't have the observation function space yet o_fnsp = output.data.function_space() u = backend.TrialFunction(o_fnsp) v = backend.TestFunction(o_fnsp) o_mass = backend.inner(u, v) * backend.dx observation_norm = backend.assemble(o_mass) diff = output.data.vector() - unperturbed.vector() growths.append( compute_norm(diff, observation_norm) / perturbation_scale) # <--- the action line if callback is not None: callback(fwd_var, output.data, unperturbed) storage = libadjoint.MemoryStorage(output) storage.set_compare(tol=None) storage.set_overwrite(True) out = adjglobals.adjointer.record_variable(fwd_var, storage) if forget: adjglobals.adjointer.forget_forward_equation(i) # can happen if we initialised a nonlinear solve with a constant zero guess if growths[0] == 0.0: return growths[1:] else: return growths
def recompute_component(self, inputs, block_variable, idx, prepared): form = prepared output = backend.assemble(form) output = create_overloaded_object(output) return output
def assemble(self): u = TrialFunction(self.V) v = TestFunction(self.V) A = inner(u, v) * dx + alpha * inner(grad(u), grad(v)) * dx return assemble(A)