def _compute_gradient(self, m, forget=True): """ Compute the functional gradient for the binary variables control array """ farm = self.solver.problem.parameters.tidal_farm # If any of the parameters changed, the forward model needs to be re-run if self.last_m is None or numpy.any(m != self.last_m): self._compute_functional(m, annotate=True) J = self.time_integrator.dolfin_adjoint_functional(self.solver.state) # Output power if self.solver.parameters.dump_period > 0: if self._solver_params.output_turbine_power: turbines = farm.turbine_cache["turbine_field"] power = self.functional.power(self.solver.state, turbines) self.power_file << project( power, farm._turbine_function_space, annotate=False) parameters = FunctionControl("turbine_friction_cache") djdtf = dolfin_adjoint.compute_gradient(J, parameters, forget=forget) dolfin.parameters["adjoint"]["stop_annotating"] = False dj = [] for tfd in farm.turbine_cache["turbine_derivative_binary"]: farm.update() dj.append(djdtf.vector().inner(tfd.vector())) dj = numpy.array(dj) return dj
def derivative(self, forget=False, **kwargs): """ Computes the first derivative of the functional with respect to its controls by solving the adjoint equations. """ log(INFO, 'Start evaluation of dj') timer = Timer("dj evaluation") self.functional = self.time_integrator.dolfin_adjoint_functional(self.solver.state) dj = compute_gradient(self.functional, self.controls, forget=forget, **kwargs) parameters["adjoint"]["stop_annotating"] = False log(INFO, "Runtime: " + str(timer.stop()) + " s") return enlisting.enlist(dj)
def derivative(self, forget=False, **kwargs): """ Computes the first derivative of the functional with respect to its controls by solving the adjoint equations. """ log(INFO, 'Start evaluation of dj') timer = Timer("dj evaluation") if not hasattr(self, "time_integrator"): self.evaluate() self.functional = self.time_integrator.dolfin_adjoint_functional( self.solver.state) dj = compute_gradient(self.functional, self.controls, forget=forget, **kwargs) parameters["adjoint"]["stop_annotating"] = False log(INFO, "Runtime: " + str(timer.stop()) + " s") return enlisting.enlist(dj)
def compute_gradient(m, forget=True): ''' Takes in the turbine positions/frictions values and computes the resulting functional 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, annotate=True) 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.power(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]) elif config.params['functional_quadrature_degree'] == 0: # Pseudo-redo the time loop to collect the necessary timestep information t = config.params["start_time"] timesteps = [t] while (t < config.params["finish_time"]): t += config.params["dt"] timesteps.append(t) if not config.params["include_time_term"]: # Remove the initial condition. I think this is a bug in dolfin-adjoint, since really I expected pop(0) here - but the Taylor tests pass only with pop(1)! timesteps.pop(1) # Construct the functional J = Functional(sum(functional.Jt(state, dummy_tf) * dt[t] for t in timesteps)) else: if not config.params["include_time_term"]: raise NotImplementedError, "Multi-steady state simulations only work with 'functional_quadrature_degree=0' or 'functional_final_time_only=True'" 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'] == 'smeared': # 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_gradient(self, m, forget=True): """ Compute the functional gradient for the turbine positions/frictions array """ farm = self.solver.problem.parameters.tidal_farm # If any of the parameters changed, the forward model needs to be re-run if self.last_m is None or numpy.any(m != self.last_m): self._compute_functional(m, annotate=True) J = self.time_integrator.dolfin_adjoint_functional(self.solver.state) # Output power if self.solver.parameters.dump_period > 0: if self._solver_params.output_turbine_power: turbines = farm.turbine_cache["turbine_field"] power = self.functional.power(self.solver.state, turbines) self.power_file << project( power, farm._turbine_function_space, annotate=False) if farm.turbine_specification.controls.dynamic_friction: parameters = [] for i in range(len(farm._parameters["friction"])): parameters.append( FunctionControl("turbine_friction_cache_t_%i" % i)) else: parameters = FunctionControl("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 farm.turbine_specification.smeared: # 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 farm.turbine_specification.controls.friction: # Compute the derivatives with respect to the turbine friction for tfd in farm.turbine_cache["turbine_derivative_friction"]: farm.update() dj.append(djdtf.vector().inner(tfd.vector())) elif farm.turbine_specification.controls.dynamic_friction: # Compute the derivatives with respect to the turbine friction for djdtf_arr, t in zip( djdtf, farm.turbine_cache["turbine_derivative_friction"]): for tfd in t: farm.update() dj.append(djdtf_arr.vector().inner(tfd.vector())) if (farm.turbine_specification.controls.position): if (farm.turbine_specification.controls.dynamic_friction): # Compute the derivatives with respect to the turbine position farm.update() turb_deriv_pos = farm.turbine_cache[ "turbine_derivative_pos"] n_time_steps = len(turb_deriv_pos) for n in range(farm.number_of_turbines): for var in ('turbine_pos_x', 'turbine_pos_y'): dj_t = 0 for t in range(n_time_steps): tfd_t = turb_deriv_pos[t][n][var] dj_t += djdtf_arr.vector().inner( tfd_t.vector()) dj.append(dj_t) else: # Compute the derivatives with respect to the turbine position for d in farm.turbine_cache["turbine_derivative_pos"]: for var in ('turbine_pos_x', 'turbine_pos_y'): farm.update() tfd = d[var] dj.append(djdtf.vector().inner(tfd.vector())) dj = numpy.array(dj) return dj
def _compute_gradient(self, m, forget=True): """ Compute the functional gradient for the turbine positions/frictions array """ farm = self.solver.problem.parameters.tidal_farm # If any of the parameters changed, the forward model needs to be re-run if numpy.any(m != self.last_m): self._compute_functional(m, annotate=True) J = self.time_integrator.dolfin_adjoint_functional() # Output power if self.solver.parameters.dump_period > 0: if self._solver_params.output_turbine_power: turbines = farm.turbine_cache["turbine_field"] power = self.functional.power(self.solver.current_state, turbines) self.power_file << project(power, farm._turbine_function_space, annotate=False) if farm.turbine_specification.controls.dynamic_friction: parameters = [] for i in xrange(len(farm._parameters["friction"])): parameters.append( FunctionControl("turbine_friction_cache_t_%i" % i)) else: parameters = FunctionControl("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 farm.turbine_specification.smeared: # 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 farm.turbine_specification.controls.friction: # Compute the derivatives with respect to the turbine friction for tfd in farm.turbine_cache["turbine_derivative_friction"]: farm.update() dj.append(djdtf.vector().inner(tfd.vector())) elif farm.turbine_specification.controls.dynamic_friction: # Compute the derivatives with respect to the turbine friction for djdtf_arr, t in zip(djdtf, farm.turbine_cache["turbine_derivative_friction"]): for tfd in t: farm.update() dj.append(djdtf_arr.vector().inner(tfd.vector())) if farm.turbine_specification.controls.position: # Compute the derivatives with respect to the turbine position for d in farm.turbine_cache["turbine_derivative_pos"]: for var in ('turbine_pos_x', 'turbine_pos_y'): farm.update() tfd = d[var] dj.append(djdtf.vector().inner(tfd.vector())) dj = numpy.array(dj) return dj
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 _derivative(self, x): _, L_tape, f = self._obj(x) control = da.Control(f) J_tape = da.compute_gradient(L_tape, control) J = np.array(J_tape.vector()[:]) return J