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 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 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 taylor_test(J, m, Jm, dJdm, HJm=None, seed=None, perturbation_direction=None, value=None): '''J must be a function that takes in a parameter value m and returns the value of the functional: func = J(m) Jm is the value of the function J at the parameter m. dJdm is the gradient of J evaluated at m, to be tested for correctness. This function returns the order of convergence of the Taylor series remainder, which should be 2 if the adjoint is working correctly. If HJm is not None, the Taylor test will also attempt to verify the correctness of the Hessian. HJm should be a callable which takes in a direction and returns the Hessian of the functional in that direction (i.e., takes in a vector and returns a vector). In that case, an additional Taylor remainder is computed, which should converge at order 3 if the Hessian is correct.''' info_blue("Running Taylor remainder convergence test ... ") import controls if isinstance(m, list): m = ListControl(m) if isinstance(m, controls.ListControl): if perturbation_direction is None: perturbation_direction = [None] * len(m.controls) if value is None: value = [None] * len(m.controls) return min(taylor_test(J, m[i], Jm, dJdm[i], HJm, seed, perturbation_direction[i], value[i]) for i in range(len(m.controls))) def get_const(val): if isinstance(val, str): return float(constant.constant_values[val]) else: return float(val) def get_value(param, value): if value is not None: return value else: try: return param.data() except libadjoint.exceptions.LibadjointErrorNeedValue: info_red("Do you need to pass forget=False to compute_gradient?") raise # First, compute perturbation sizes. seed_default = 0.01 if seed is None: if isinstance(m, controls.ConstantControl): seed = get_const(m.a) / 5.0 if seed == 0.0: seed = 0.1 elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) if len(ic.vector()) == 1: # our control is in R seed = float(ic) / 5.0 else: seed = seed_default else: seed = seed_default perturbation_sizes = [seed/(2.0**i) for i in range(5)] # Next, compute the perturbation direction. if perturbation_direction is None: if isinstance(m, controls.ConstantControl): perturbation_direction = 1 elif isinstance(m, controls.ConstantControls): perturbation_direction = numpy.array([get_const(x)/5.0 for x in m.v]) elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) perturbation_direction = backend.Function(ic.function_space()) compatibility.randomise(perturbation_direction) else: raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to compute a perturbation direction") else: if isinstance(m, controls.FunctionControl): ic = get_value(m, value) # So now compute the perturbations: if not isinstance(perturbation_direction, backend.Function): perturbations = [x*perturbation_direction for x in perturbation_sizes] else: perturbations = [] for x in perturbation_sizes: perturbation = backend.Function(perturbation_direction) vec = perturbation.vector() vec *= x perturbations.append(perturbation) # And now the perturbed inputs: if isinstance(m, controls.ConstantControl): pinputs = [backend.Constant(get_const(m.a) + x) for x in perturbations] elif isinstance(m, controls.ConstantControls): a = numpy.array([get_const(x) for x in m.v]) def make_const(arr): return [backend.Constant(x) for x in arr] pinputs = [make_const(a + x) for x in perturbations] elif isinstance(m, controls.FunctionControl): pinputs = [] for x in perturbations: pinput = backend.Function(x) vec = pinput.vector() vec += ic.vector() pinputs.append(pinput) # Issue 34: We must evaluate HJm before we evaluate the tape at the # perturbed controls below. if HJm is not None: HJm_values = [] for perturbation in perturbations: HJmp = HJm(perturbation) HJm_values.append(HJmp) # At last: the common bit! functional_values = [] for pinput in pinputs: Jp = J(pinput) functional_values.append(Jp) # First-order Taylor remainders (not using adjoint) no_gradient = [abs(perturbed_J - Jm) for perturbed_J in functional_values] info("Taylor remainder without gradient information: " + str(no_gradient)) info("Convergence orders for Taylor remainder without gradient information (should all be 1): " + str(convergence_order(no_gradient))) with_gradient = [] for i in range(len(perturbations)): if isinstance(m, controls.ConstantControl) or isinstance(m, controls.ConstantControls): remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i]) else: remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i], ic=ic) with_gradient.append(remainder) if min(with_gradient + no_gradient) < 1e-16: warning("Warning: The Taylor remainders are close to machine precision (< %s). Try increasing the seed value in case the Taylor remainder test fails." % min(with_gradient + no_gradient)) info("Taylor remainder with gradient information: " + str(with_gradient)) info("Convergence orders for Taylor remainder with gradient information (should all be 2): " + str(convergence_order(with_gradient))) if HJm is not None: with_hessian = [] if isinstance(m, controls.ConstantControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - float(dJdm)*perturbations[i] - 0.5*perturbations[i]*HJm_values[i]) with_hessian.append(remainder) elif isinstance(m, controls.ConstantControls): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - numpy.dot(dJdm, perturbations[i]) - 0.5*numpy.dot(perturbations[i], HJm_values[i])) with_hessian.append(remainder) elif isinstance(m, controls.FunctionControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - dJdm.vector().inner(perturbations[i].vector()) - 0.5*perturbations[i].vector().inner(HJm_values[i].vector())) with_hessian.append(remainder) info("Taylor remainder with Hessian information: " + str(with_hessian)) info("Convergence orders for Taylor remainder with Hessian information (should all be 3): " + str(convergence_order(with_hessian))) return min(convergence_order(with_hessian)) else: return min(convergence_order(with_gradient))
def _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value, size=None): from . import function, controls # Default to five runs/perturbations is none given if size is None: size = 5 # Check inputs if not isinstance(m, libadjoint.Parameter): raise ValueError("m must be a valid control instance.") def get_const(val): if isinstance(val, str): return float(constant.constant_values[val]) else: return float(val) def get_value(param, value): if value is not None: return value else: try: return param.data() except libadjoint.exceptions.LibadjointErrorNeedValue: info_red("Do you need to pass forget=False to compute_gradient?") raise # First, compute perturbation sizes. seed_default = 0.01 if seed is None: if isinstance(m, controls.ConstantControl): seed = get_const(m.a) / 5.0 if seed == 0.0: seed = 0.1 elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) if len(ic.vector()) == 1: # our control is in R seed = float(ic) / 5.0 else: seed = seed_default else: seed = seed_default perturbation_sizes = [seed/(2.0**i) for i in range(size)] # Next, compute the perturbation direction. if perturbation_direction is None: if isinstance(m, controls.ConstantControl): perturbation_direction = 1 elif isinstance(m, controls.ConstantControls): perturbation_direction = numpy.array([get_const(x)/5.0 for x in m.v]) elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) # Check for MultiMeshFunction_space if isinstance(ic.function_space(), compatibility.multi_mesh_function_space_type): perturbation_direction = backend.MultiMeshFunction(ic.function_space()) else: perturbation_direction = function.Function(ic.function_space()) compatibility.randomise(perturbation_direction) else: raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to compute a perturbation direction") else: if isinstance(m, controls.FunctionControl): ic = get_value(m, value) elif isinstance(m, controls.ConstantControl): perturbation_direction = float(perturbation_direction) # So now compute the perturbations: if not isinstance(perturbation_direction, (backend.Function, backend.MultiMeshFunction)): perturbations = [x*perturbation_direction for x in perturbation_sizes] else: perturbations = [] for x in perturbation_sizes: perturbation = perturbation_direction.copy(deepcopy=True) vec = perturbation.vector() vec *= x perturbations.append(perturbation) # And now the perturbed inputs: if isinstance(m, controls.ConstantControl): pinputs = [backend.Constant(get_const(m.a) + x) for x in perturbations] elif isinstance(m, controls.ConstantControls): a = numpy.array([get_const(x) for x in m.v]) def make_const(arr): return [backend.Constant(x) for x in arr] pinputs = [make_const(a + x) for x in perturbations] elif isinstance(m, controls.FunctionControl): pinputs = [] for x in perturbations: pinput = x.copy(deepcopy=True) vec = pinput.vector() vec += ic.vector() pinputs.append(pinput) # Issue 34: We must evaluate HJm before we evaluate the tape at the # perturbed controls below. if HJm is not None: HJm_values = [] for perturbation in perturbations: HJmp = HJm(perturbation) HJm_values.append(HJmp) # At last: the common bit! functional_values = [] for pinput in pinputs: Jp = J(pinput) functional_values.append(Jp) # First-order Taylor remainders (not using adjoint) no_gradient = [abs(perturbed_J - Jm) for perturbed_J in functional_values] info("Taylor remainder without gradient information: " + str(no_gradient)) info("Convergence orders for Taylor remainder without gradient information (should all be 1): " + str(convergence_order(no_gradient))) with_gradient = [] for i in range(len(perturbations)): if isinstance(m, controls.ConstantControl) or isinstance(m, controls.ConstantControls): remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i]) else: remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i], ic=ic) with_gradient.append(remainder) if min(with_gradient + no_gradient) < 1e-16: warning("Warning: The Taylor remainders are close to machine precision (< %s). Try increasing the seed value in case the Taylor remainder test fails." % min(with_gradient + no_gradient)) info("Taylor remainder with gradient information: " + str(with_gradient)) info("Convergence orders for Taylor remainder with gradient information (should all be 2): " + str(convergence_order(with_gradient))) if HJm is not None: with_hessian = [] if isinstance(m, controls.ConstantControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - float(dJdm)*perturbations[i] - 0.5*perturbations[i]*HJm_values[i]) with_hessian.append(remainder) elif isinstance(m, controls.ConstantControls): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - numpy.dot(dJdm, perturbations[i]) - 0.5*numpy.dot(perturbations[i], HJm_values[i])) with_hessian.append(remainder) elif isinstance(m, controls.FunctionControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - dJdm.vector().inner(perturbations[i].vector()) - 0.5*perturbations[i].vector().inner(HJm_values[i].vector())) with_hessian.append(remainder) info("Taylor remainder with Hessian information: " + str(with_hessian)) info("Convergence orders for Taylor remainder with Hessian information (should all be 3): " + str(convergence_order(with_hessian))) return min(convergence_order(with_hessian)) else: return min(convergence_order(with_gradient))
def _taylor_test_single_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value): import function # Check inputs if not isinstance(m, libadjoint.Parameter): raise ValueError, "m must be a valid control instance." def get_const(val): if isinstance(val, str): return float(constant.constant_values[val]) else: return float(val) def get_value(param, value): if value is not None: return value else: try: return param.data() except libadjoint.exceptions.LibadjointErrorNeedValue: info_red("Do you need to pass forget=False to compute_gradient?") raise # First, compute perturbation sizes. seed_default = 0.01 if seed is None: if isinstance(m, controls.ConstantControl): seed = get_const(m.a) / 5.0 if seed == 0.0: seed = 0.1 elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) if len(ic.vector()) == 1: # our control is in R seed = float(ic) / 5.0 else: seed = seed_default else: seed = seed_default perturbation_sizes = [seed/(2.0**i) for i in range(5)] # Next, compute the perturbation direction. if perturbation_direction is None: if isinstance(m, controls.ConstantControl): perturbation_direction = 1 elif isinstance(m, controls.ConstantControls): perturbation_direction = numpy.array([get_const(x)/5.0 for x in m.v]) elif isinstance(m, controls.FunctionControl): ic = get_value(m, value) # Check for MultiMeshFunction_space if isinstance(ic.function_space(), compatibility.multi_mesh_function_space_type): perturbation_direction = backend.MultiMeshFunction(ic.function_space()) else: perturbation_direction = function.Function(ic.function_space()) compatibility.randomise(perturbation_direction) else: raise libadjoint.exceptions.LibadjointErrorNotImplemented("Don't know how to compute a perturbation direction") else: if isinstance(m, controls.FunctionControl): ic = get_value(m, value) elif isinstance(m, controls.ConstantControl): perturbation_direction = float(perturbation_direction) # So now compute the perturbations: if not isinstance(perturbation_direction, (backend.Function, backend.MultiMeshFunction)): perturbations = [x*perturbation_direction for x in perturbation_sizes] else: perturbations = [] for x in perturbation_sizes: perturbation = perturbation_direction.copy(deepcopy=True) vec = perturbation.vector() vec *= x perturbations.append(perturbation) # And now the perturbed inputs: if isinstance(m, controls.ConstantControl): pinputs = [backend.Constant(get_const(m.a) + x) for x in perturbations] elif isinstance(m, controls.ConstantControls): a = numpy.array([get_const(x) for x in m.v]) def make_const(arr): return [backend.Constant(x) for x in arr] pinputs = [make_const(a + x) for x in perturbations] elif isinstance(m, controls.FunctionControl): pinputs = [] for x in perturbations: pinput = x.copy(deepcopy=True) vec = pinput.vector() vec += ic.vector() pinputs.append(pinput) # Issue 34: We must evaluate HJm before we evaluate the tape at the # perturbed controls below. if HJm is not None: HJm_values = [] for perturbation in perturbations: HJmp = HJm(perturbation) HJm_values.append(HJmp) # At last: the common bit! functional_values = [] for pinput in pinputs: Jp = J(pinput) functional_values.append(Jp) # First-order Taylor remainders (not using adjoint) no_gradient = [abs(perturbed_J - Jm) for perturbed_J in functional_values] info("Taylor remainder without gradient information: " + str(no_gradient)) info("Convergence orders for Taylor remainder without gradient information (should all be 1): " + str(convergence_order(no_gradient))) with_gradient = [] for i in range(len(perturbations)): if isinstance(m, controls.ConstantControl) or isinstance(m, controls.ConstantControls): remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i]) else: remainder = taylor_remainder_with_gradient(m, Jm, dJdm, functional_values[i], perturbations[i], ic=ic) with_gradient.append(remainder) if min(with_gradient + no_gradient) < 1e-16: warning("Warning: The Taylor remainders are close to machine precision (< %s). Try increasing the seed value in case the Taylor remainder test fails." % min(with_gradient + no_gradient)) info("Taylor remainder with gradient information: " + str(with_gradient)) info("Convergence orders for Taylor remainder with gradient information (should all be 2): " + str(convergence_order(with_gradient))) if HJm is not None: with_hessian = [] if isinstance(m, controls.ConstantControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - float(dJdm)*perturbations[i] - 0.5*perturbations[i]*HJm_values[i]) with_hessian.append(remainder) elif isinstance(m, controls.ConstantControls): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - numpy.dot(dJdm, perturbations[i]) - 0.5*numpy.dot(perturbations[i], HJm_values[i])) with_hessian.append(remainder) elif isinstance(m, controls.FunctionControl): for i in range(len(perturbations)): remainder = abs(functional_values[i] - Jm - dJdm.vector().inner(perturbations[i].vector()) - 0.5*perturbations[i].vector().inner(HJm_values[i].vector())) with_hessian.append(remainder) info("Taylor remainder with Hessian information: " + str(with_hessian)) info("Convergence orders for Taylor remainder with Hessian information (should all be 3): " + str(convergence_order(with_hessian))) return min(convergence_order(with_hessian)) else: return min(convergence_order(with_gradient))