def __init__(self, config, scale=1.0, forward_model=sw_model.sw_solve, plot=False): ''' If plot is True, the functional values will be automatically saved in a plot. scale is ignored if automatic_scaling is active. ''' # Hide the configuration since changes would break the memoize algorithm. self.__config__ = config self.scale = scale self.automatic_scaling_factor = None self.plot = plot # Caching variables that store which controls the last forward run was performed self.last_m = None self.last_state = None if self.__config__.params["dump_period"] > 0: self.turbine_file = File("turbines.pvd", "compressed") if config.params['output_turbine_power']: self.power_file = File("power.pvd", "compressed") class Parameter: def data(self): m = [] if config.params["turbine_parametrisation"] == "smooth": m = numpy.zeros(config.turbine_function_space.dim()) else: if 'turbine_friction' in config.params["controls"]: m += list(config.params['turbine_friction']) if 'turbine_pos' in config.params["controls"]: m += numpy.reshape(config.params['turbine_pos'], -1).tolist() return numpy.array(m) self.parameter = [Parameter()] if plot: self.plotter = AnimatedPlot(xlabel="Iteration", ylabel="Functional value") def compute_functional(m, return_final_state=False): ''' Takes in the turbine positions/frictions values and computes the resulting functional of interest. ''' self.last_m = m self.update_turbine_cache(m) tf = config.turbine_cache.cache["turbine_field"] #info_green("Turbine integral: %f ", assemble(tf*dx)) #info_green("The correct integral should be: %f ", 25.2771) # computed with wolfram alpha using: # int 0.17353373* (exp(-1.0/(1-(x/10)**2)) * exp(-1.0/(1-(y/10)**2)) * exp(2)) dx dy, x=-10..10, y=-10..10 #info_red("relative error: %f", (assemble(tf*dx)-25.2771)/25.2771) return compute_functional_from_tf(tf, return_final_state) def compute_functional_from_tf(tf, return_final_state): ''' Takes in the turbine friction field and computes the resulting functional of interest. ''' adj_reset() parameters["adjoint"]["record_all"] = True # Get initial conditions if config.params["implicit_turbine_thrust_parametrisation"]: state = Function(config.function_space_2enriched, name="Current_state") elif config.params["turbine_thrust_parametrisation"]: state = Function(config.function_space_enriched, name="Current_state") else: state = Function(config.function_space, name="Current_state") if config.params["steady_state"] and self.last_state != None: # Speed up the nonlinear solves by starting the Newton solve with the most recent state solution state.assign(self.last_state, annotate=False) else: ic = config.params['initial_condition'] state.assign(ic, annotate=False) # Solve the shallow water system functional = config.functional(config) j = forward_model(config, state, functional=functional, turbine_field=tf) self.last_state = state if return_final_state: return j, state else: return j def compute_gradient(m, forget=True): ''' Takes in the turbine positions/frictions values and computes the resulting functional gradient. ''' myt = Timer("full compute_gradient") # If the last forward run was performed with the same parameters, then all recorded values by dolfin-adjoint are still valid for this adjoint run # and we do not have to rerun the forward model. if numpy.any(m != self.last_m): compute_functional(m) state = self.last_state functional = config.functional(config) # Produce power plot if config.params['output_turbine_power']: if config.params['turbine_thrust_parametrisation'] or config.params[ "implicit_turbine_thrust_parametrisation"] or "dynamic_turbine_friction" in config.params[ "controls"]: info_red( "Turbine power VTU's is not yet implemented with thrust based turbines parameterisations and dynamic turbine friction control." ) else: turbines = self.__config__.turbine_cache.cache[ "turbine_field"] self.power_file << project(functional.expr( state, turbines), config.turbine_function_space, annotate=False) # The functional depends on the turbine friction function which we do not have on scope here. # But dolfin-adjoint only cares about the name, so we can just create a dummy function with the desired name. dummy_tf = Function(FunctionSpace(state.function_space().mesh(), "R", 0), name="turbine_friction") if config.params['steady_state'] or config.params[ "functional_final_time_only"]: J = Functional( functional.Jt(state, dummy_tf) * dt[FINISH_TIME]) else: J = Functional(functional.Jt(state, dummy_tf) * dt) if 'dynamic_turbine_friction' in config.params["controls"]: parameters = [ InitialConditionParameter("turbine_friction_cache_t_%i" % i) for i in range(len(config.params["turbine_friction"])) ] else: parameters = InitialConditionParameter( "turbine_friction_cache") djdtf = dolfin_adjoint.compute_gradient(J, parameters, forget=forget) dolfin.parameters["adjoint"]["stop_annotating"] = False # Decide if we need to apply the chain rule to get the gradient of interest if config.params['turbine_parametrisation'] == 'smooth': # We are looking for the gradient with respect to the friction dj = dolfin_adjoint.optimization.get_global(djdtf) else: # Let J be the functional, m the parameter and u the solution of the PDE equation F(u) = 0. # Then we have # dJ/dm = (\partial J)/(\partial u) * (d u) / d m + \partial J / \partial m # = adj_state * \partial F / \partial u + \partial J / \partial m # In this particular case m = turbine_friction, J = \sum_t(ft) dj = [] if 'turbine_friction' in config.params["controls"]: # Compute the derivatives with respect to the turbine friction for tfd in config.turbine_cache.cache[ "turbine_derivative_friction"]: config.turbine_cache.update(config) dj.append(djdtf.vector().inner(tfd.vector())) elif 'dynamic_turbine_friction' in config.params["controls"]: # Compute the derivatives with respect to the turbine friction for djdtf_arr, t in zip( djdtf, config.turbine_cache. cache["turbine_derivative_friction"]): for tfd in t: config.turbine_cache.update(config) dj.append(djdtf_arr.vector().inner(tfd.vector())) if 'turbine_pos' in config.params["controls"]: # Compute the derivatives with respect to the turbine position for d in config.turbine_cache.cache[ "turbine_derivative_pos"]: for var in ('turbine_pos_x', 'turbine_pos_y'): config.turbine_cache.update(config) tfd = d[var] dj.append(djdtf.vector().inner(tfd.vector())) dj = numpy.array(dj) return dj def compute_hessian_action(m, m_dot): if numpy.any(m != self.last_m): self.run_adjoint_model_mem(m, forget=False) state = self.last_state functional = config.functional(config) if config.params['steady_state'] or config.params[ "functional_final_time_only"]: J = Functional(functional.Jt(state) * dt[FINISH_TIME]) else: J = Functional(functional.Jt(state) * dt) H = drivers.hessian(J, InitialConditionParameter("friction"), warn=False) m_dot = project(Constant(1), config.turbine_function_space) return H(m_dot) self.compute_functional_mem = memoize.MemoizeMutable( compute_functional) self.compute_gradient_mem = memoize.MemoizeMutable(compute_gradient) self.compute_hessian_action_mem = memoize.MemoizeMutable( compute_hessian_action)