def _ad_assign_numpy(dst, src, offset): dst.assign( backend.Constant( numpy.reshape(src[offset:offset + dst.value_size()], dst.ufl_shape))) offset += dst.value_size() return dst, offset
def test_scalar_parameters_adjoint(J, a, dJda, seed=0.1): info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ") functional_values = [] f_direct = J(a) a = numpy.array([float(x) for x in a]) dJda = numpy.array(dJda) perturbation_direction = a/5.0 perturbation_sizes = [seed / (2**i) for i in range(5)] perturbations = [a * i for i in perturbation_sizes] for x in perturbations: da = [backend.Constant(a[i] + x[i]) for i in range(len(a))] functional_values.append(J(da)) # First-order Taylor remainders (not using adjoint) no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values] info("Taylor remainder without adjoint information: " + str(no_gradient)) info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient))) with_gradient = [] for i in range(len(perturbations)): remainder = abs(functional_values[i] - f_direct - numpy.dot(dJda, perturbations[i])) with_gradient.append(remainder) info("Taylor remainder with adjoint information: " + str(with_gradient)) info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient))) return min(convergence_order(with_gradient))
def __copy_data(self, m): """Returns a deep copy of the given Function/Constant.""" if hasattr(m, "vector"): return backend.Function(m.function_space()) elif hasattr(m, "value_size"): return backend.Constant(m(())) else: raise TypeError('Unknown control type %s.' % str(type(m)))
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
def postprocess(dJdparam, project, list_type): if isinstance(dJdparam, list): return delist([postprocess(x, project, list_type) for x in dJdparam], list_type=list_type) else: if project: dJdparam = project_test(dJdparam) if isinstance(dJdparam, float): dJdparam = backend.Constant(dJdparam) return dJdparam
def evaluate_tlm_component(self, inputs, tlm_inputs, block_variable, idx, prepared=None): F_form = prepared["form"] dFdu = prepared["dFdu"] V = self.get_outputs()[idx].output.function_space() bcs = [] dFdm = 0. dFdm_shape = 0. for block_variable in self.get_dependencies(): tlm_value = block_variable.tlm_value c = block_variable.output c_rep = block_variable.saved_output if isinstance(c, backend.DirichletBC): if tlm_value is None: bcs.append(compat.create_bc(c, homogenize=True)) else: bcs.append(tlm_value) continue elif isinstance(c, compat.MeshType): X = backend.SpatialCoordinate(c) c_rep = X if tlm_value is None: continue if c == self.func and not self.linear: continue if isinstance(c, compat.MeshType): dFdm_shape += compat.assemble_adjoint_value( backend.derivative(-F_form, c_rep, tlm_value)) else: dFdm += backend.derivative(-F_form, c_rep, tlm_value) if isinstance(dFdm, float): v = dFdu.arguments()[0] dFdm = backend.inner(backend.Constant(numpy.zeros(v.ufl_shape)), v) * backend.dx dFdm = compat.assemble_adjoint_value(dFdm) + dFdm_shape dudm = backend.Function(V) return self._assemble_and_solve_tlm_eq( compat.assemble_adjoint_value(dFdu, bcs=bcs), dFdm, dudm, bcs)
def _taylor_test_multi_control(J, m, Jm, dJdm, HJm, seed, perturbation_direction, value, size=None): if perturbation_direction is None: perturbation_direction = [None] * len(m.controls) perturbation_direction = enlist(perturbation_direction) if value is None: value = [None] * len(m.controls) value = enlist(value) # Create a deep copy of the initial control values m_cpy = [] for c in m: if isinstance(c.data(), backend.Function): m_cpy.append(c.data().copy(deepcopy=True)) elif isinstance(c.data(), backend.MultiMeshFunction): m_cpy.append(c.data().copy(deepcopy=True)) else: m_cpy.append(backend.Constant(c.data())) # Build a objective version restricted to the i'th control def J_cmp(J, i): m_values = list(m_cpy) def out(x): m_values[i] = x return J(m_values) return out # A Hessian version restricted to the i'th control if HJm is not None: HJm_cmp = lambda i: lambda x: HJm.__class__(HJm.J, m[i])(x) else: HJm_cmp = lambda i: None # Perform the Taylor tests for each control min_conv = 1e10 for i in range(len(m.controls)): print("\nRunning Taylor test for control {}".format(i)) conv = _taylor_test_single_control(J_cmp(J, i), m[i], Jm, dJdm[i], HJm_cmp(i), seed, perturbation_direction[i], value[i], size) min_conv = min(min_conv, conv) return min_conv
def scale(obj, factor): """ A generic function to scale Functions, Constants and lists, numpy arrays, ... """ if hasattr(obj, "function_space"): # dolfin.Function of dolfin.MultiMeshFunctionSpace if isinstance(obj.function_space(), compatibility.multi_mesh_function_space_type): scaled_obj = backend.MultiMeshFunction(obj.function_space(), factor * obj.vector()) else: scaled_obj = backend.Function(obj.function_space(), factor * obj.vector()) elif isinstance(obj, backend.Constant): # dolfin.Constant scaled_obj = backend.Constant(factor * constant_to_array(obj)) else: # Lists, numpy arrays, ... try: scaled_obj = factor * obj except TypeError: # Lists of dolfin objects? scaled_obj = [scale(o, factor) for o in obj] return scaled_obj
def test_scalar_parameter_adjoint(J, a, dJda, seed=None): info_blue("Running Taylor remainder convergence analysis for the adjoint model ... ") functional_values = [] f_direct = J(a) if seed is None: seed = float(a)/5.0 if seed == 0.0: seed = 0.1 perturbations = [seed / (2**i) for i in range(5)] for da in (backend.Constant(float(a) + x) for x in perturbations): functional_values.append(J(da)) # First-order Taylor remainders (not using adjoint) no_gradient = [abs(perturbed_f - f_direct) for perturbed_f in functional_values] info("Taylor remainder without adjoint information: " + str(no_gradient)) info("Convergence orders for Taylor remainder without adjoint information (should all be 1): " + str(convergence_order(no_gradient))) with_gradient = [] gradient_fd = [] for i in range(len(perturbations)): gradient_fd.append((functional_values[i] - f_direct)/perturbations[i]) remainder = abs(functional_values[i] - f_direct - float(dJda)*perturbations[i]) with_gradient.append(remainder) info("Taylor remainder with adjoint information: " + str(with_gradient)) info("Convergence orders for Taylor remainder with adjoint information (should all be 2): " + str(convergence_order(with_gradient))) info("Gradients (finite differencing): " + str(gradient_fd)) info("Gradient (adjoint): " + str(dJda)) return min(convergence_order(with_gradient))
def compute_gradient(J, param, forget=True, ignore=[], callback=lambda var, output: None, project=False): if not isinstance(J, Functional): raise ValueError("J must be of type dolfin_adjoint.Functional.") flag = misc.pause_annotation() enlisted_controls = enlist(param) param = ListControl(enlisted_controls) if backend.parameters["adjoint"]["allow_zero_derivatives"]: dJ_init = [] for c in enlisted_controls: if isinstance(c.data(), backend.Constant): dJ_init.append(backend.Constant(0)) elif isinstance(c.data(), backend.Function): space = c.data().function_space() dJ_init.append(backend.Function(space)) else: dJ_init = [None] * len(enlisted_controls) dJdparam = enlisted_controls.__class__(dJ_init) last_timestep = adjglobals.adjointer.timestep_count ignorelist = [] for fn in ignore: if isinstance(fn, backend.Function): ignorelist.append(adjglobals.adj_variables[fn]) elif isinstance(fn, str): ignorelist.append(libadjoint.Variable(fn, 0, 0)) else: ignorelist.append(fn) for i in range(adjglobals.adjointer.timestep_count): adjglobals.adjointer.set_functional_dependencies(J, i) for i in range(adjglobals.adjointer.equation_count)[::-1]: fwd_var = adjglobals.adjointer.get_forward_variable(i) if fwd_var in ignorelist: info("Ignoring the adjoint equation for %s" % fwd_var) continue (adj_var, output) = adjglobals.adjointer.get_adjoint_solution(i, J) callback(adj_var, output.data) storage = libadjoint.MemoryStorage(output) storage.set_overwrite(True) adjglobals.adjointer.record_variable(adj_var, storage) fwd_var = libadjoint.Variable(adj_var.name, adj_var.timestep, adj_var.iteration) out = param.equation_partial_derivative(adjglobals.adjointer, output.data, i, fwd_var) dJdparam = _add(dJdparam, out) if last_timestep > adj_var.timestep: # We have hit a new timestep, and need to compute this timesteps' \partial J/\partial m contribution out = param.functional_partial_derivative(adjglobals.adjointer, J, adj_var.timestep) dJdparam = _add(dJdparam, out) last_timestep = adj_var.timestep if forget is None: pass elif forget: adjglobals.adjointer.forget_adjoint_equation(i) else: adjglobals.adjointer.forget_adjoint_values(i) rename(J, dJdparam, param) misc.continue_annotation(flag) return postprocess(dJdparam, project, list_type=enlisted_controls)
def update_constants(d): for constant in d: name = constant.adj_name if name not in scalar_parameters: backend.Constant.assign(constant_objects[name], backend.Constant(d[constant]))
def make_const(arr): return [backend.Constant(x) for x in arr]
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 derivative(self, adjointer, variable, dependencies, values): if self.verbose: for dep in dependencies: print(variable.timestep, "derive wrt ", dep.name) if self.regform is not None and variable.name == self.regform.coefficients( )[0].name(): # derivative wrt the contorl if self.verbose: " derivatives wrt the controls " raise RuntimeError("""The derivative of a regularisation term doesn't work properly and shouldn't be used""" ) return self.regfunc.derivative(adjointer, variable, dependencies, values) else: # transate finish_time: UGLY!! if "FINISH_TIME" in self.times: final_time = _time_levels(adjointer, adjointer.timestep_count - 1)[1] self.times[self.times.index("FINISH_TIME")] = final_time if self.verbose: print("derive ", variable.timestep, " num values ", len(values)) timesteps = self._derivative_timesteps(adjointer, variable) ff = [backend.Constant(0.0)] * len(self.coords) for i in range(len(self.coords)): if self.skip[i]: if self.verbose: print("skipped") else: if len(timesteps ) is 1: # only occurs at start and finish time tsoi = timesteps[-1] if tsoi is 0: toi = _time_levels(adjointer, tsoi)[0] ind = -1 else: toi = _time_levels(adjointer, tsoi)[-1] ind = 0 else: if len(values) is 1: # one value (easy) tsoi = timesteps[-1] toi = _time_levels(adjointer, tsoi)[0] ind = 0 elif len(values) is 2: # two values (hard) tsoi = timesteps[-1] toi = _time_levels(adjointer, tsoi)[0] if _time_levels(adjointer, tsoi)[1] in self.times: ind = 0 else: ind = 1 else: # three values (easy) tsoi = timesteps[1] toi = _time_levels(adjointer, tsoi)[0] ind = 1 coef = values[ind].data ref = self.refs[i][self.times.index(toi)] if self.index[i] is None: solu = coef(self.coords[i]) else: solu = coef(self.coords[i])[self.index[i]] ff[i] = backend.Constant(self.alpha * 2.0 * (solu - float(ref))) # Set up linear combinations to be projected form = ff[0] * self.basis[0] for i in range(1, len(self.coords)): form += ff[i] * self.basis[i] v = backend.project(form, self.func.function_space()) return adjlinalg.Vector(v)