def dj(self, m, forget, optimisation_iteration=True): ''' This memoised function returns the gradient of the functional for the parameter choice m. ''' info_green('Start evaluation of dj') timer = dolfin.Timer("dj evaluation") dj = self.compute_gradient_mem(m, forget) # We assume that the gradient is computed at and only at the beginning of each new optimisation iteration. # Hence, this is the right moment to store the turbine friction field and to increment the optimisation iteration # counter. if optimisation_iteration: self.__config__.optimisation_iteration += 1 if self.__config__.params["dump_period"] > 0: # A cache hit skips the turbine cache update, so we need # trigger it manually. if self.compute_gradient_mem.has_cache(m, forget): self.update_turbine_cache(m) if "dynamic_turbine_friction" in self.__config__.params["controls"]: info_red("Turbine VTU output not yet implemented for dynamic turbine control") else: self.turbine_file << self.__config__.turbine_cache.cache["turbine_field"] # Compute the total amount of friction due to turbines if self.__config__.params["turbine_parametrisation"] == "smeared": print "Total amount of friction: ", assemble(self.__config__.turbine_cache.cache["turbine_field"] * dx) if self.save_functional_values and MPI.process_number() == 0: with open("functional_values.txt", "a") as functional_values: functional_values.write(str(self.last_j) + "\n") if self.plot: self.plotter.addPoint(self.last_j) self.plotter.savefig("functional_plot.png") if self.__config__.params["save_checkpoints"]: self.save_checkpoint("checkpoint") # Compute the scaling factor if never done before if self.__config__.params['automatic_scaling'] and not self.automatic_scaling_factor: if not 'turbine_pos' in self.__config__.params['controls']: raise NotImplementedError("Automatic scaling only works if the turbine positions are control parameters") if len(self.__config__.params['controls']) > 1: assert(len(dj) % 3 == 0) # Exclude the first third from the automatic scaling as it contains the friction coefficients djl2 = max(abs(dj[len(dj) / 3:])) else: djl2 = max(abs(dj)) if djl2 == 0: raise ValueError("Automatic scaling failed: The gradient at the parameter point is zero") else: self.automatic_scaling_factor = abs(self.__config__.params['automatic_scaling_multiplier'] * max(self.__config__.params['turbine_x'], self.__config__.params['turbine_y']) / djl2 / self.scale) info_blue("The automatic scaling factor was set to " + str(self.automatic_scaling_factor * self.scale) + ".") info_blue('Runtime: ' + str(timer.stop()) + " s") info_green('|dj| = ' + str(numpy.linalg.norm(dj))) if self.__config__.params['automatic_scaling']: return dj * self.scale * self.automatic_scaling_factor else: return dj * self.scale
def dj(self, m, forget): ''' This memoised function returns the gradient of the functional for the parameter choice m. ''' info_green('Start evaluation of dj') timer = dolfin.Timer("dj evaluation") dj = self.compute_gradient_mem(m, forget) # We assume that at the gradient is computed if and only if at the beginning of each new optimisation iteration. # Hence, let this is the right moment to store the turbine friction field. if self.__config__.params["dump_period"] > 0: # A cache hit skips the turbine cache update, so we need # trigger it manually. if self.compute_gradient_mem.has_cache(m, forget): self.update_turbine_cache(m) if "dynamic_turbine_friction" in self.__config__.params[ "controls"]: info_red( "Turbine VTU output not yet implemented for dynamic turbine control" ) else: self.turbine_file << self.__config__.turbine_cache.cache[ "turbine_field"] if self.__config__.params["save_checkpoints"]: self.save_checkpoint("checkpoint") # Compute the scaling factor if never done before if self.__config__.params[ 'automatic_scaling'] and not self.automatic_scaling_factor: if not 'turbine_pos' in self.__config__.params['controls']: raise NotImplementedError, "Automatic scaling only works if the turbine positions are control parameters" if len(self.__config__.params['controls']) > 1: assert (len(dj) % 3 == 0) # Exclude the first third from the automatic scaling as it contains the friction coefficients djl2 = max(abs(dj[len(dj) / 3:])) else: djl2 = max(abs(dj)) if djl2 == 0: raise ValueError, "Automatic scaling failed: The gradient at the parameter point is zero" else: self.automatic_scaling_factor = abs( self.__config__.params['automatic_scaling_multiplier'] * max(self.__config__.params['turbine_x'], self.__config__.params['turbine_y']) / djl2 / self.scale) info_blue("The automatic scaling factor was set to " + str(self.automatic_scaling_factor * self.scale) + ".") info_blue('Runtime: ' + str(timer.stop()) + " s") info_green('|dj| = ' + str(numpy.linalg.norm(dj))) if self.__config__.params['automatic_scaling']: return dj * self.scale * self.automatic_scaling_factor else: return dj * self.scale
def dj_with_check(self, m, seed=0.1, tol=1.8, forget=True): ''' This function checks the correctness and returns the gradient of the functional for the parameter choice m. ''' info_red("Checking derivative at m = " + str(m)) p = numpy.random.rand(len(m)) minconv = helpers.test_gradient_array(self.j, self.dj, m, seed=seed, perturbation_direction=p) if minconv < tol: info_red("The gradient taylor remainder test failed.") sys.exit(1) else: info_green("The gradient taylor remainder test passed.") return self.dj(m, forget)
def dj(self, m, forget): ''' This memoised function returns the gradient of the functional for the parameter choice m. ''' info_green('Start evaluation of dj') timer = dolfin.Timer("dj evaluation") dj = self.compute_gradient_mem(m, forget) # We assume that at the gradient is computed if and only if at the beginning of each new optimisation iteration. # Hence, let this is the right moment to store the turbine friction field. if self.__config__.params["dump_period"] > 0: # A cache hit skips the turbine cache update, so we need # trigger it manually. if self.compute_gradient_mem.has_cache(m, forget): self.update_turbine_cache(m) if "dynamic_turbine_friction" in self.__config__.params["controls"]: info_red("Turbine VTU output not yet implemented for dynamic turbine control") else: self.turbine_file << self.__config__.turbine_cache.cache["turbine_field"] if self.__config__.params["save_checkpoints"]: self.save_checkpoint("checkpoint") # Compute the scaling factor if never done before if self.__config__.params['automatic_scaling'] and not self.automatic_scaling_factor: if not 'turbine_pos' in self.__config__.params['controls']: raise NotImplementedError, "Automatic scaling only works if the turbine positions are control parameters" if len(self.__config__.params['controls']) > 1: assert(len(dj) % 3 == 0) # Exclude the first third from the automatic scaling as it contains the friction coefficients djl2 = max(abs(dj[len(dj)/3:])) else: djl2 = max(abs(dj)) if djl2 == 0: raise ValueError, "Automatic scaling failed: The gradient at the parameter point is zero" else: self.automatic_scaling_factor = abs(self.__config__.params['automatic_scaling_multiplier'] * max(self.__config__.params['turbine_x'], self.__config__.params['turbine_y']) / djl2 / self.scale) info_blue("The automatic scaling factor was set to " + str(self.automatic_scaling_factor * self.scale) + ".") info_blue('Runtime: ' + str(timer.stop()) + " s") info_green('|dj| = ' + str(numpy.linalg.norm(dj))) if self.__config__.params['automatic_scaling']: return dj * self.scale * self.automatic_scaling_factor else: return dj * self.scale
def set_domain(self, domain, warning=True): if warning and hasattr(self, 'domain'): info_red("If you are overwriting the domain, make sure that you reapply the boundary conditions as well") self.domain = domain # Define the subdomain for the turbine site. The default value should only be changed for smeared turbine representations. domains = CellFunction("size_t", self.domain.mesh) domains.set_all(1) self.site_dx = Measure("dx")[domains] # The measure used to integrate the turbine friction V, H = self.finite_element(self.domain.mesh) T = FunctionSpace(self.domain.mesh, 'CG', 2) # Turbine space self.turbine_function_space = T self.function_space = MixedFunctionSpace([V, H]) self.function_space_enriched = MixedFunctionSpace([V, H, T]) self.function_space_2enriched = MixedFunctionSpace([V, H, T, T])
def set_domain(self, domain, warning=True): if warning: info_red( "If you are overwriting the domain, make sure that you reapply the boundary conditions as well" ) self.domain = domain # Define the subdomain for the turbine site. The default value should only be changed for smooth turbine representations. domains = CellFunction("size_t", self.domain.mesh) domains.set_all(1) self.site_dx = Measure("dx")[domains] V, H = self.finite_element(self.domain.mesh) T = FunctionSpace(self.domain.mesh, 'CG', 2) # Turbine space self.turbine_function_space = T self.function_space = MixedFunctionSpace([V, H]) self.function_space_enriched = MixedFunctionSpace([V, H, T]) self.function_space_2enriched = MixedFunctionSpace([V, H, T, T])
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(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 sw_solve(config, state, turbine_field=None, functional=None, annotate=True, u_source=None): '''Solve the shallow water equations with the parameters specified in params. Options for linear_solver and preconditioner are: linear_solver: lu, cholesky, cg, gmres, bicgstab, minres, tfqmr, richardson preconditioner: none, ilu, icc, jacobi, bjacobi, sor, amg, additive_schwarz, hypre_amg, hypre_euclid, hypre_parasails, ml_amg ''' ############################### Setting up the equations ########################### # Define variables for all used parameters ds = config.domain.ds params = config.params # To begin with, check if the provided parameters are valid params.check() theta = params["theta"] dt = params["dt"] g = params["g"] depth = params["depth"] # Reset the time params["current_time"] = params["start_time"] t = params["current_time"] quadratic_friction = params["quadratic_friction"] include_advection = params["include_advection"] include_diffusion = params["include_diffusion"] include_time_term = params["include_time_term"] diffusion_coef = params["diffusion_coef"] newton_solver = params["newton_solver"] picard_relative_tolerance = params["picard_relative_tolerance"] picard_iterations = params["picard_iterations"] linear_solver = params["linear_solver"] preconditioner = params["preconditioner"] bctype = params["bctype"] strong_bc = params["strong_bc"] free_slip_on_sides = params["free_slip_on_sides"] steady_state = params["steady_state"] functional_final_time_only = params["functional_final_time_only"] functional_quadrature_degree = params["functional_quadrature_degree"] is_nonlinear = (include_advection or quadratic_friction) turbine_thrust_parametrisation = params["turbine_thrust_parametrisation"] implicit_turbine_thrust_parametrisation = params["implicit_turbine_thrust_parametrisation"] cache_forward_state = params["cache_forward_state"] if not 0 <= functional_quadrature_degree <= 1: raise ValueError("functional_quadrature_degree must be 0 or 1.") if implicit_turbine_thrust_parametrisation: function_space = config.function_space_2enriched elif turbine_thrust_parametrisation: function_space = config.function_space_enriched else: function_space = config.function_space # Take care of the steady state case if steady_state: dt = 1. params["finish_time"] = params["start_time"] + dt / 2 theta = 1. # Define test functions if implicit_turbine_thrust_parametrisation: v, q, o, o_adv = TestFunctions(function_space) elif turbine_thrust_parametrisation: v, q, o = TestFunctions(function_space) else: v, q = TestFunctions(function_space) # Define functions state_new = Function(function_space, name="New_state") # solution of the next timestep state_nl = Function(function_space, name="Best_guess_state") # the last computed state of the next timestep, used for the picard iteration if not newton_solver and (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): raise NotImplementedError("Thrust turbine representation does currently only work with the newton solver.") # Split mixed functions if is_nonlinear and newton_solver: if implicit_turbine_thrust_parametrisation: u, h, up_u, up_u_adv = split(state_new) elif turbine_thrust_parametrisation: u, h, up_u = split(state_new) else: u, h = split(state_new) else: u, h = TrialFunctions(function_space) if implicit_turbine_thrust_parametrisation: u0, h0, up_u0, up_u_adv0 = split(state) elif turbine_thrust_parametrisation: u0, h0, up_u0 = split(state) else: u0, h0 = split(state) u_nl, h_nl = split(state_nl) # Create initial conditions and interpolate state_new.assign(state, annotate=annotate) # u_(n+theta) and h_(n+theta) u_mid = (1.0 - theta) * u0 + theta * u h_mid = (1.0 - theta) * h0 + theta * h # If a picard iteration is used we need an intermediate state if is_nonlinear and not newton_solver: u_nl, h_nl = split(state_nl) state_nl.assign(state, annotate=annotate) u_mid_nl = (1.0 - theta) * u0 + theta * u_nl # The normal direction n = FacetNormal(function_space.mesh()) # Mass matrix M = inner(v, u) * dx M += inner(q, h) * dx M0 = inner(v, u0) * dx M0 += inner(q, h0) * dx # Divergence term. Ct_mid = -depth * inner(u_mid, grad(q)) * dx #+inner(avg(u_mid),jump(q,n))*dS # This term is only needed for dg element pairs if bctype == 'dirichlet': if steady_state: raise ValueError("Can not use a time dependent boundary condition for a steady state simulation") # The dirichlet boundary condition on the left hand side expr = config.params["weak_dirichlet_bc_expr"] bc_contr = - depth * dot(expr, n) * q * ds(1) # The dirichlet boundary condition on the right hand side bc_contr -= depth * dot(expr, n) * q * ds(2) # We enforce a no-normal flow on the sides by removing the surface integral. # bc_contr -= dot(u_mid, n) * q * ds(3) elif bctype == 'flather': if steady_state: raise ValueError("Can not use a time dependent boundary condition for a steady state simulation") # The Flather boundary condition on the left hand side expr = config.params["flather_bc_expr"] bc_contr = - depth * dot(expr, n) * q * ds(1) Ct_mid += sqrt(g * depth) * inner(h_mid, q) * ds(1) # The contributions of the Flather boundary condition on the right hand side Ct_mid += sqrt(g * depth) * inner(h_mid, q) * ds(2) elif bctype == 'strong_dirichlet': # Do not replace anything in the surface integrals as the strong Dirichlet Boundary condition will do that bc_contr = -depth * dot(u_mid, n) * q * ds(1) bc_contr -= depth * dot(u_mid, n) * q * ds(2) if not free_slip_on_sides: bc_contr -= depth * dot(u_mid, n) * q * ds(3) else: info_red("Unknown boundary condition type: %s" % bctype) sys.exit(1) # Pressure gradient operator C_mid = g * inner(v, grad(h_mid)) * dx #+inner(avg(v),jump(h_mid,n))*dS # This term is only needed for dg element pairs # Bottom friction friction = params["friction"] if turbine_field: if type(turbine_field) == list: tf = Function(turbine_field[0], name="turbine_friction", annotate=annotate) else: tf = Function(turbine_field, name="turbine_friction", annotate=annotate) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print0("Adding thrust force") # Compute the upstream velocities if implicit_turbine_thrust_parametrisation: up_u_eq = upstream_u_implicit_equation(config, tf, u, up_u, o, up_u_adv, o_adv) elif turbine_thrust_parametrisation: up_u_eq = upstream_u_equation(config, tf, u, up_u, o) def thrust_force(up_u, min=smooth_uflmin): ''' Returns the thrust force for a given upstream velcocity ''' # Now apply a pointwise transformation based on the interpolation of a loopup table c_T_coeffs = [0.08344535, -1.42428216, 9.13153605, -26.19370168, 28.8752054] c_T_coeffs.reverse() c_T = min(0.88, sum([c_T_coeffs[i] * up_u ** i for i in range(len(c_T_coeffs))])) # The amount of forcing we want to apply turbine_radius = 15. A_c = pi * Constant(turbine_radius ** 2) # Turbine cross section f = 0.5 * c_T * up_u ** 2 * A_c return f # Apply the force in the opposite direction of the flow f_dir = -thrust_force(up_u) * u / norm_approx(u, alpha=1e-6) # Distribute this force over the turbine area thrust = inner(f_dir * tf / (Constant(config.turbine_cache.turbine_integral()) * config.params["depth"]), v) * dx # Friction term # With Newton we can simply use a non-linear form if quadratic_friction and newton_solver: R_mid = friction / depth * dot(u_mid, u_mid) ** 0.5 * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid, u_mid) ** 0.5 * inner(u_mid, v) * config.site_dx(1) # With a picard iteration we need to linearise using the best guess elif quadratic_friction and not newton_solver: R_mid = friction / depth * dot(u_mid_nl, u_mid_nl) ** 0.5 * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid_nl, u_mid_nl) ** 0.5 * inner(u_mid, v) * config.site_dx(1) # Use a linear drag else: R_mid = friction / depth * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * inner(u_mid, v) * config.site_dx(1) # Advection term # With a newton solver we can simply use a quadratic form if include_advection and newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid), v) * dx # With a picard iteration we need to linearise using the best guess if include_advection and not newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid_nl), v) * dx if include_diffusion: # Check that we are not using a DG velocity function space, as the facet integrals are not implemented. if "Discontinuous" in str(function_space.split()[0]): raise NotImplementedError("The diffusion term for discontinuous elements is not implemented yet.") D_mid = diffusion_coef * inner(grad(u_mid), grad(v)) * dx # Create the final form G_mid = C_mid + Ct_mid + R_mid # Add the advection term if include_advection: G_mid += Ad_mid # Add the diffusion term if include_diffusion: G_mid += D_mid # Add the source term if u_source: G_mid -= inner(u_source, v) * dx F = dt * G_mid - dt * bc_contr # Add the time term if include_time_term and not steady_state: F += M - M0 if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: F += up_u_eq if turbine_field: F -= thrust # Preassemble the lhs if possible use_lu_solver = (linear_solver == "lu") if not is_nonlinear: lhs_preass = assemble(dolfin.lhs(F)) # Precompute the LU factorisation if use_lu_solver: info("Computing the LU factorisation for later use ...") if bctype == 'strong_dirichlet': raise NotImplementedError("Strong boundary condition and reusing LU factorisation is currently not implemented") lu_solver = LUSolver(lhs_preass) lu_solver.parameters["reuse_factorization"] = True solver_parameters = {"linear_solver": linear_solver, "preconditioner": preconditioner} # Do some parameter checking: if "dynamic_turbine_friction" in params["controls"]: if len(config.params["turbine_friction"]) != (params["finish_time"] - t) / dt + 1: print0("You control the turbine friction dynamically, but your turbine friction parameter is not an array of length 'number of timesteps' (here: %i)." % ((params["finish_time"] - t) / dt + 1)) import sys sys.exit(1) ############################### Perform the simulation ########################### if params["dump_period"] > 0: try: statewriter_cb = config.statewriter_callback except AttributeError: statewriter_cb = None writer = StateWriter(config, optimisation_iteration=config.optimisation_iteration, callback=statewriter_cb) if not steady_state and include_time_term: print0("Writing state to disk...") writer.write(state) step = 0 if functional is not None: if steady_state or functional_final_time_only: j = 0. if params["print_individual_turbine_power"]: j_individual = [0] * len(params["turbine_pos"]) force_individual = [0] * len(params["turbine_pos"]) else: if functional_quadrature_degree == 0: quad = 0.0 else: quad = 0.5 j = dt * quad * assemble(functional.Jt(state, tf)) if params["print_individual_turbine_power"]: j_individual = [] force_individual = [] for i in range(len(params["turbine_pos"])): j_individual.append(dt * quad * assemble(functional.Jt_individual(state, i))) force_individual.append(dt * quad * assemble(functional.force_individual(state, i))) print0("Start of time loop") adjointer.time.start(t) timestep = 0 while (t < params["finish_time"]): timestep += 1 t += dt params["current_time"] = t # Update bc's if bctype == "strong_dirichlet": strong_bc.update_time(t) else: expr.t = t - (1.0 - theta) * dt # Update source term if u_source: u_source.t = t - (1.0 - theta) * dt step += 1 # Solve non-linear system with a Newton sovler if is_nonlinear and newton_solver: # Use a Newton solver to solve the nonlinear problem. #solver_parameters["linear_solver"] = "gmres" #solver_parameters["linear_solver"] = "superlu_dist" #solver_parameters["preconditioner"] = "ilu" # does not work in parallel #solver_parameters["preconditioner"] = "amg" #solver_parameters["linear_solver"] = "mumps" #solver_parameters["linear_solver"] = "umfpack" solver_parameters["newton_solver"] = {} solver_parameters["newton_solver"]["error_on_nonconvergence"] = True solver_parameters["newton_solver"]["maximum_iterations"] = 20 solver_parameters["newton_solver"]["convergence_criterion"] = "incremental" solver_parameters["newton_solver"]["relative_tolerance"] = 1e-16 if cache_forward_state and state_cache.has_key(t): print0("Load initial guess from cache for time %f." % t) # Load initial guess for solver from cache state_new.assign(state_cache[t], annotate=False) elif not include_time_term: print0("Set the initial guess for the nonlinear solver to the initial condition.") # Reset the initial guess after each timestep ic = config.params['initial_condition'] state_new.assign(ic, annotate=False) info_blue("Solve shallow water equations at time %s (Newton iteration) ..." % params["current_time"]) if bctype == 'strong_dirichlet': solve(F == 0, state_new, bcs=strong_bc.bcs, solver_parameters=solver_parameters, annotate=annotate) else: solve(F == 0, state_new, solver_parameters=solver_parameters, annotate=annotate) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print0("Inflow velocity: ", u[0]((10, 160))) print0("Estimated upstream velocity: ", up_u((640. / 3, 160))) print0("Expected thrust force: ", thrust_force(u[0]((10, 160)), min=min)((0))) print0("Total amount of thurst force applied: ", assemble(inner(Constant(1), thrust_force(up_u) * tf / config.turbine_cache.turbine_integral()) * dx)) us.append(u[0]((10, 160))) thrusts.append(thrust_force(u[0]((10, 160)))((0))) thrusts_est.append(assemble(inner(Constant(1), thrust_force(up_u) * tf / config.turbine_cache.turbine_integral()) * dx)) import matplotlib.pyplot as plt plt.clf() plt.plot(us, thrusts, label="Analytical") plt.plot(us, thrusts_est, label="Approximated") plt.legend(loc=2) plt.savefig("thrust_plot.pdf", format='pdf') # Solve non-linear system with a Picard iteration elif is_nonlinear: # Solve the problem using a picard iteration iter_counter = 0 while True: info_blue("Solving shallow water equations at time %s (Picard iteration %d) ..." % (params["current_time"], iter_counter)) if bctype == 'strong_dirichlet': solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, bcs=strong_bc.bcs, solver_parameters=solver_parameters) else: solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, solver_parameters=solver_parameters, annotate=annotate) iter_counter += 1 if iter_counter > 0: relative_diff = abs(assemble(inner(state_new - state_nl, state_new - state_nl) * dx)) / assemble(inner(state_new, state_new) * dx) info_blue("Picard iteration " + str(iter_counter) + " relative difference: " + str(relative_diff)) if relative_diff < picard_relative_tolerance: info("Picard iteration converged after " + str(iter_counter) + " iterations.") break elif iter_counter >= picard_iterations: info_red("Picard iteration reached maximum number of iterations (" + str(picard_iterations) + ") with a relative difference of " + str(relative_diff) + ".") break state_nl.assign(state_new) # Solve linear system with preassembled matrices else: # dolfin can't assemble empty forms which can sometimes happen here. # A simple workaround is to add a dummy term: dummy_term = Constant(0) * q * dx rhs_preass = assemble(dolfin.rhs(F + dummy_term)) # Apply dirichlet boundary conditions info_blue("Solving shallow water equations at time %s (preassembled matrices) ..." % (params["current_time"])) if bctype == 'strong_dirichlet': [bc.apply(lhs_preass, rhs_preass) for bc in strong_bc.bcs] if use_lu_solver: info("Using a LU solver to solve the linear system.") lu_solver.solve(state.vector(), rhs_preass, annotate=annotate) else: solve(lhs_preass, state_new.vector(), rhs_preass, solver_parameters["linear_solver"], solver_parameters["preconditioner"], annotate=annotate) # After the timestep solve, update state state.assign(state_new) if cache_forward_state: # Save state for initial guess cache print0("Cache initial guess for time %f." % t) if not state_cache.has_key(t): state_cache[t] = Function(state_new.function_space()) state_cache[t].assign(state_new, annotate=False) # Set the control function for the upcoming timestep. if turbine_field: if type(turbine_field) == list: tf.assign(turbine_field[timestep]) else: tf.assign(turbine_field) if params["dump_period"] > 0 and step % params["dump_period"] == 0: print0("Write state to disk...") writer.write(state) if functional is not None: if not (functional_final_time_only and t < params["finish_time"]): if steady_state or functional_final_time_only or functional_quadrature_degree == 0: quad = 1.0 elif t >= params["finish_time"]: quad = 0.5 * dt else: quad = 1.0 * dt j += quad * assemble(functional.Jt(state, tf)) if params["print_individual_turbine_power"]: info_green("Computing individual turbine power extraction contribution...") individual_contribution_list = ['x_pos', 'y_pos', 'turbine_power', 'total_force_on_turbine', 'turbine_friction'] fr_individual = range(len(params["turbine_pos"])) for i in range(len(params["turbine_pos"])): j_individual[i] += dt * quad * assemble(functional.Jt_individual(state, i)) force_individual[i] += dt * quad * assemble(functional.force_individual(state, i)) if len(params["turbine_friction"]) > 0: fr_individual[i] = params["turbine_friction"][i] else: fr_individual = [params["turbine_friction"]] * len(params["turbine_pos"]) individual_contribution_list.append((params["turbine_pos"][i])[0]) individual_contribution_list.append((params["turbine_pos"][i])[1]) individual_contribution_list.append(j_individual[i]) individual_contribution_list.append(force_individual[i]) individual_contribution_list.append(fr_individual[i]) print0("Contribution of turbine number %d at co-ordinates:" % (i + 1), params["turbine_pos"][i], ' is: ', j_individual[i] * 0.001, 'kW', 'with friction of', fr_individual[i]) # Increase the adjoint timestep adj_inc_timestep(time=t, finished=(not t < params["finish_time"])) print0("End of time loop.") # Write the turbine positions, power extraction and friction to a .csv file named turbine_info.csv if params['print_individual_turbine_power']: f = config.params['base_path'] + os.path.sep + "iter_" + str(config.optimisation_iteration) + '/' # Save the very first result in a different file if config.optimisation_iteration == 0 and not os.path.isfile(f): f += 'initial_turbine_info.csv' else: f += 'turbine_info.csv' output_turbines = open(f, 'w') for i in range(0, len(individual_contribution_list), 5): print >> output_turbines, '%s, %s, %s, %s, %s' % (individual_contribution_list[i], individual_contribution_list[i + 1], individual_contribution_list[i + 2], individual_contribution_list[i + 3], individual_contribution_list[i + 4]) print 'Total of individual turbines is', sum(j_individual) if functional is not None: return j
def sw_solve(config, state, turbine_field=None, functional=None, annotate=True, u_source=None): '''Solve the shallow water equations with the parameters specified in params. Options for linear_solver and preconditioner are: linear_solver: lu, cholesky, cg, gmres, bicgstab, minres, tfqmr, richardson preconditioner: none, ilu, icc, jacobi, bjacobi, sor, amg, additive_schwarz, hypre_amg, hypre_euclid, hypre_parasails, ml_amg ''' ############################### Setting up the equations ########################### # Define variables for all used parameters ds = config.domain.ds params = config.params # To begin with, check if the provided parameters are valid params.check() theta = params["theta"] dt = params["dt"] g = params["g"] depth = params["depth"] # Reset the time params["current_time"] = params["start_time"] t = params["current_time"] quadratic_friction = params["quadratic_friction"] include_advection = params["include_advection"] include_diffusion = params["include_diffusion"] diffusion_coef = params["diffusion_coef"] newton_solver = params["newton_solver"] picard_relative_tolerance = params["picard_relative_tolerance"] picard_iterations = params["picard_iterations"] run_benchmark = params["run_benchmark"] solver_exclude = params["solver_exclude"] linear_solver = params["linear_solver"] preconditioner = params["preconditioner"] bctype = params["bctype"] strong_bc = params["strong_bc"] free_slip_on_sides = params["free_slip_on_sides"] steady_state = params["steady_state"] functional_final_time_only = params["functional_final_time_only"] is_nonlinear = (include_advection or quadratic_friction) turbine_thrust_parametrisation = params["turbine_thrust_parametrisation"] implicit_turbine_thrust_parametrisation = params[ "implicit_turbine_thrust_parametrisation"] if implicit_turbine_thrust_parametrisation: function_space = config.function_space_2enriched elif turbine_thrust_parametrisation: function_space = config.function_space_enriched else: function_space = config.function_space # Take care of the steady state case if steady_state: dt = 1. params["finish_time"] = params["start_time"] + dt / 2 theta = 1. # Define test functions if implicit_turbine_thrust_parametrisation: v, q, o, o_adv = TestFunctions(function_space) elif turbine_thrust_parametrisation: v, q, o = TestFunctions(function_space) else: v, q = TestFunctions(function_space) # Define functions state_new = Function(function_space, name="New_state") # solution of the next timestep state_nl = Function( function_space, name="Best_guess_state" ) # the last computed state of the next timestep, used for the picard iteration if not newton_solver and (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): raise NotImplementedError, "Thrust turbine representation does currently only work with the newton solver." # Split mixed functions if is_nonlinear and newton_solver: if implicit_turbine_thrust_parametrisation: u, h, up_u, up_u_adv = split(state_new) elif turbine_thrust_parametrisation: u, h, up_u = split(state_new) else: u, h = split(state_new) else: u, h = TrialFunctions(function_space) if implicit_turbine_thrust_parametrisation: u0, h0, up_u0, up_u_adv0 = split(state) elif turbine_thrust_parametrisation: u0, h0, up_u0 = split(state) else: u0, h0 = split(state) u_nl, h_nl = split(state_nl) # Create initial conditions and interpolate state_new.assign(state, annotate=annotate) # u_(n+theta) and h_(n+theta) u_mid = (1.0 - theta) * u0 + theta * u h_mid = (1.0 - theta) * h0 + theta * h # If a picard iteration is used we need an intermediate state if is_nonlinear and not newton_solver: u_nl, h_nl = split(state_nl) state_nl.assign(state, annotate=annotate) u_mid_nl = (1.0 - theta) * u0 + theta * u_nl # The normal direction n = FacetNormal(function_space.mesh()) # Mass matrix M = inner(v, u) * dx M += inner(q, h) * dx M0 = inner(v, u0) * dx M0 += inner(q, h0) * dx # Divergence term. Ct_mid = -depth * inner(u_mid, grad(q)) * dx #+inner(avg(u_mid),jump(q,n))*dS # This term is only needed for dg element pairs if bctype == 'dirichlet': if steady_state: raise ValueError, "Can not use a time dependent boundary condition for a steady state simulation" # The dirichlet boundary condition on the left hand side ufl = Expression( ("eta0*sqrt(g/depth)*cos(k*x[0]-sqrt(g*depth)*k*t)", "0"), eta0=params["eta0"], g=g, depth=depth, t=t, k=params["k"]) bc_contr = -depth * dot(ufl, n) * q * ds(1) # The dirichlet boundary condition on the right hand side bc_contr -= depth * dot(ufl, n) * q * ds(2) # We enforce a no-normal flow on the sides by removing the surface integral. # bc_contr -= dot(u_mid, n) * q * ds(3) elif bctype == 'flather': if steady_state: raise ValueError, "Can not use a time dependent boundary condition for a steady state simulation" # The Flather boundary condition on the left hand side ufl = Expression(("2*eta0*sqrt(g/depth)*cos(-sqrt(g*depth)*k*t)", "0"), eta0=params["eta0"], g=g, depth=depth, t=t, k=params["k"]) bc_contr = -depth * dot(ufl, n) * q * ds(1) Ct_mid += sqrt(g * depth) * inner(h_mid, q) * ds(1) # The contributions of the Flather boundary condition on the right hand side Ct_mid += sqrt(g * depth) * inner(h_mid, q) * ds(2) elif bctype == 'strong_dirichlet': # Do not replace anything in the surface integrals as the strong Dirichlet Boundary condition will do that bc_contr = -depth * dot(u_mid, n) * q * ds(1) bc_contr -= depth * dot(u_mid, n) * q * ds(2) if not free_slip_on_sides: bc_contr -= depth * dot(u_mid, n) * q * ds(3) else: info_red("Unknown boundary condition type: %s" % bctype) sys.exit(1) # Pressure gradient operator C_mid = g * inner(v, grad(h_mid)) * dx #+inner(avg(v),jump(h_mid,n))*dS # This term is only needed for dg element pairs # Bottom friction class FrictionExpr(Expression): def eval(self, value, x): value[0] = params["friction"] friction = FrictionExpr() if turbine_field: if type(turbine_field) == list: tf = Function(turbine_field[0], name="turbine_friction", annotate=annotate) else: tf = Function(turbine_field, name="turbine_friction", annotate=annotate) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print "Adding thrust force" # Compute the upstream velocities if implicit_turbine_thrust_parametrisation: up_u_eq = upstream_u_implicit_equation(config, tf, u, up_u, o, up_u_adv, o_adv) elif turbine_thrust_parametrisation: up_u_eq = upstream_u_equation(config, tf, u, up_u, o) def thrust_force(up_u, min=smooth_uflmin): ''' Returns the thrust force for a given upstream velcocity ''' # Now apply a pointwise transformation based on the interpolation of a loopup table c_T_coeffs = [ 0.08344535, -1.42428216, 9.13153605, -26.19370168, 28.8752054 ] c_T_coeffs.reverse() c_T = min( 0.88, sum([ c_T_coeffs[i] * up_u**i for i in range(len(c_T_coeffs)) ])) # The amount of forcing we want to apply turbine_radius = 15. A_c = pi * Constant(turbine_radius**2) # Turbine cross section f = 0.5 * c_T * up_u**2 * A_c return f # Apply the force in the opposite direction of the flow f_dir = -thrust_force(up_u) * u / norm_approx(u, alpha=1e-6) # Distribute this force over the turbine area thrust = inner( f_dir * tf / (Constant(config.turbine_cache.turbine_integral()) * config.params["depth"]), v) * dx # Friction term # With Newton we can simply use a non-linear form if quadratic_friction and newton_solver: R_mid = friction / depth * dot(u_mid, u_mid)**0.5 * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid, u_mid)**0.5 * inner( u_mid, v) * config.site_dx(1) # With a picard iteration we need to linearise using the best guess elif quadratic_friction and not newton_solver: R_mid = friction / depth * dot(u_mid_nl, u_mid_nl)**0.5 * inner( u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid_nl, u_mid_nl)**0.5 * inner( u_mid, v) * config.site_dx(1) # Use a linear drag else: R_mid = friction / depth * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * inner(u_mid, v) * config.site_dx(1) # Advection term # With a newton solver we can simply use a quadratic form if include_advection and newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid), v) * dx # With a picard iteration we need to linearise using the best guess if include_advection and not newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid_nl), v) * dx if include_diffusion: # Check that we are not using a DG velocity function space, as the facet integrals are not implemented. if "Discontinuous" in str(function_space.split()[0]): raise NotImplementedError, "The diffusion term for discontinuous elements is not implemented yet." D_mid = diffusion_coef * inner(grad(u_mid), grad(v)) * dx # Create the final form G_mid = C_mid + Ct_mid + R_mid # Add the advection term if include_advection: G_mid += Ad_mid # Add the diffusion term if include_diffusion: G_mid += D_mid # Add the source term if u_source: G_mid -= inner(u_source, v) * dx F = dt * G_mid - dt * bc_contr # Add the time term if not steady_state: F += M - M0 if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: F += up_u_eq if turbine_field: F -= thrust # Preassemble the lhs if possible use_lu_solver = (linear_solver == "lu") if not quadratic_friction and not include_advection: lhs_preass = assemble(dolfin.lhs(F)) # Precompute the LU factorisation if use_lu_solver: info("Computing the LU factorisation for later use ...") if bctype == 'strong_dirichlet': raise NotImplementedError, "Strong boundary condition and reusing LU factorisation is currently not implemented" lu_solver = LUSolver(lhs_preass) lu_solver.parameters["reuse_factorization"] = True solver_parameters = { "linear_solver": linear_solver, "preconditioner": preconditioner } # Do some parameter checking: if "dynamic_turbine_friction" in params["controls"]: if len(config.params["turbine_friction"] ) != (params["finish_time"] - t) / dt + 1: print "You control the turbine friction dynamically, but your turbine friction parameter is not an array of length 'number of timesteps' (here: %i)." % ( (params["finish_time"] - t) / dt + 1) import sys sys.exit() ############################### Perform the simulation ########################### if params["dump_period"] > 0: writer = helpers.StateWriter(config) print0("Writing state to disk...") writer.write(state) step = 0 if functional is not None: if steady_state or functional_final_time_only: j = 0. else: quad = 0.5 j = dt * quad * assemble(functional.Jt(state, tf)) print0("Starting time loop...") adjointer.time.start(t) timestep = 0 while (t < params["finish_time"]): timestep += 1 t += dt params["current_time"] = t # Update bc's if bctype == "strong_dirichlet": strong_bc.update_time(t) else: ufl.t = t - (1.0 - theta) * dt # Update source term if u_source: u_source.t = t - (1.0 - theta) * dt step += 1 # Solve non-linear system with a Newton sovler if is_nonlinear and newton_solver: # Use a Newton solver to solve the nonlinear problem. #solver_parameters["linear_solver"] = "gmres" #solver_parameters["linear_solver"] = "superlu_dist" #solver_parameters["preconditioner"] = "ilu" # does not work in parallel #solver_parameters["preconditioner"] = "amg" #solver_parameters["linear_solver"] = "mumps" solver_parameters["newton_solver"] = {} #solver_parameters["newton_solver"]["error_on_nonconvergence"] = False solver_parameters["newton_solver"]["maximum_iterations"] = 20 solver_parameters["newton_solver"][ "convergence_criterion"] = "incremental" solver_parameters["newton_solver"]["relative_tolerance"] = 1e-16 if bctype == 'strong_dirichlet': solver_benchmark.solve(F == 0, state_new, bcs=strong_bc.bcs, solver_parameters=solver_parameters, annotate=annotate, benchmark=run_benchmark, solve=solve, solver_exclude=solver_exclude) else: solver_benchmark.solve(F == 0, state_new, solver_parameters=solver_parameters, annotate=annotate, benchmark=run_benchmark, solve=solve, solver_exclude=solver_exclude) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print "Inflow velocity: ", u[0]((10, 160)) print "Estimated upstream velocity: ", up_u((640. / 3, 160)) print "Expected thrust force: ", thrust_force(u[0]((10, 160)), min=min)((0)) print "Total amount of thurst force applied: ", assemble( inner( Constant(1), thrust_force(up_u) * tf / config.turbine_cache.turbine_integral()) * dx) us.append(u[0]((10, 160))) thrusts.append(thrust_force(u[0]((10, 160)))((0))) thrusts_est.append( assemble( inner( Constant(1), thrust_force(up_u) * tf / config.turbine_cache.turbine_integral()) * dx)) import matplotlib.pyplot as plt plt.clf() plt.plot(us, thrusts, label="Analytical") plt.plot(us, thrusts_est, label="Approximated") plt.legend(loc=2) plt.savefig("thrust_plot.pdf", format='pdf') # Solve non-linear system with a Picard iteration elif is_nonlinear: # Solve the problem using a picard iteration iter_counter = 0 while True: if bctype == 'strong_dirichlet': solver_benchmark.solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, bcs=strong_bc.bcs, solver_parameters=solver_parameters, annotate=annotate, benchmark=run_benchmark, solve=solve, solver_exclude=solver_exclude) else: solver_benchmark.solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, solver_parameters=solver_parameters, annotate=annotate, benchmark=run_benchmark, solve=solve, solver_exclude=solver_exclude) iter_counter += 1 if iter_counter > 0: relative_diff = abs( assemble( inner(state_new - state_nl, state_new - state_nl) * dx)) / assemble(inner(state_new, state_new) * dx) info_blue("Picard iteration " + str(iter_counter) + " relative difference: " + str(relative_diff)) if relative_diff < picard_relative_tolerance: info("Picard iteration converged after " + str(iter_counter) + " iterations.") break elif iter_counter >= picard_iterations: info_red( "Picard iteration reached maximum number of iterations (" + str(picard_iterations) + ") with a relative difference of " + str(relative_diff) + ".") break state_nl.assign(state_new) # Solve linear system with preassembled matrices else: rhs_preass = assemble(dolfin.rhs(F)) # Apply dirichlet boundary conditions if bctype == 'strong_dirichlet': [bc.apply(lhs_preass, rhs_preass) for bc in strong_bc.bcs] if use_lu_solver: info("Using a LU solver to solve the linear system.") lu_solver.solve(state.vector(), rhs_preass, annotate=annotate) else: solver_benchmark.solve(lhs_preass, state_new.vector(), rhs_preass, solver_parameters["linear_solver"], solver_parameters["preconditioner"], annotate=annotate, benchmark=run_benchmark, solve=solve, solver_exclude=solver_exclude) # After the timestep solve, update state state.assign(state_new) # Set the control function for the upcoming timestep. if turbine_field: if type(turbine_field) == list: tf.assign(turbine_field[timestep]) else: tf.assign(turbine_field) if params["dump_period"] > 0 and step % params["dump_period"] == 0: print0("Writing state to disk...") writer.write(state) if functional is not None: if not (functional_final_time_only and t < params["finish_time"]): if steady_state or functional_final_time_only: quad = 1.0 elif t >= params["finish_time"]: quad = 0.5 * dt else: quad = 1.0 * dt j += quad * assemble(functional.Jt(state, tf)) # Increase the adjoint timestep adj_inc_timestep(time=t, finished=not t < params["finish_time"]) print0("New timestep t = %f" % t) print0("Ending time loop.") if functional is not None: return j
def sw_solve(config, state, turbine_field=None, functional=None, annotate=True, u_source = None): '''Solve the shallow water equations with the parameters specified in params. Options for linear_solver and preconditioner are: linear_solver: lu, cholesky, cg, gmres, bicgstab, minres, tfqmr, richardson preconditioner: none, ilu, icc, jacobi, bjacobi, sor, amg, additive_schwarz, hypre_amg, hypre_euclid, hypre_parasails, ml_amg ''' ############################### Setting up the equations ########################### # Define variables for all used parameters ds = config.domain.ds params = config.params # To begin with, check if the provided parameters are valid params.check() theta = params["theta"] dt = params["dt"] g = params["g"] depth = params["depth"] # Reset the time params["current_time"] = params["start_time"] t = params["current_time"] quadratic_friction = params["quadratic_friction"] include_advection = params["include_advection"] include_diffusion = params["include_diffusion"] diffusion_coef = params["diffusion_coef"] newton_solver = params["newton_solver"] picard_relative_tolerance = params["picard_relative_tolerance"] picard_iterations = params["picard_iterations"] run_benchmark = params["run_benchmark"] solver_exclude = params["solver_exclude"] linear_solver = params["linear_solver"] preconditioner = params["preconditioner"] bctype = params["bctype"] strong_bc = params["strong_bc"] free_slip_on_sides = params["free_slip_on_sides"] steady_state = params["steady_state"] functional_final_time_only = params["functional_final_time_only"] is_nonlinear = (include_advection or quadratic_friction) turbine_thrust_parametrisation = params["turbine_thrust_parametrisation"] implicit_turbine_thrust_parametrisation = params["implicit_turbine_thrust_parametrisation"] if implicit_turbine_thrust_parametrisation: function_space = config.function_space_2enriched elif turbine_thrust_parametrisation: function_space = config.function_space_enriched else: function_space = config.function_space # Take care of the steady state case if steady_state: dt = 1. params["finish_time"] = params["start_time"] + dt/2 theta = 1. # Define test functions if implicit_turbine_thrust_parametrisation: v, q, o, o_adv = TestFunctions(function_space) elif turbine_thrust_parametrisation: v, q, o = TestFunctions(function_space) else: v, q = TestFunctions(function_space) # Define functions state_new = Function(function_space, name="New_state") # solution of the next timestep state_nl = Function(function_space, name="Best_guess_state") # the last computed state of the next timestep, used for the picard iteration if not newton_solver and (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): raise NotImplementedError, "Thrust turbine representation does currently only work with the newton solver." # Split mixed functions if is_nonlinear and newton_solver: if implicit_turbine_thrust_parametrisation: u, h, up_u, up_u_adv = split(state_new) elif turbine_thrust_parametrisation: u, h, up_u = split(state_new) else: u, h = split(state_new) else: u, h = TrialFunctions(function_space) if implicit_turbine_thrust_parametrisation: u0, h0, up_u0, up_u_adv0 = split(state) elif turbine_thrust_parametrisation: u0, h0, up_u0 = split(state) else: u0, h0 = split(state) u_nl, h_nl = split(state_nl) # Create initial conditions and interpolate state_new.assign(state, annotate=annotate) # u_(n+theta) and h_(n+theta) u_mid = (1.0-theta)*u0 + theta*u h_mid = (1.0-theta)*h0 + theta*h # If a picard iteration is used we need an intermediate state if is_nonlinear and not newton_solver: u_nl, h_nl = split(state_nl) state_nl.assign(state, annotate=annotate) u_mid_nl = (1.0-theta)*u0 + theta*u_nl # The normal direction n = FacetNormal(function_space.mesh()) # Mass matrix M = inner(v, u) * dx M += inner(q, h) * dx M0 = inner(v, u0) * dx M0 += inner(q, h0) * dx # Divergence term. Ct_mid = -depth * inner(u_mid, grad(q))*dx #+inner(avg(u_mid),jump(q,n))*dS # This term is only needed for dg element pairs if bctype == 'dirichlet': if steady_state: raise ValueError, "Can not use a time dependent boundary condition for a steady state simulation" # The dirichlet boundary condition on the left hand side ufl = Expression(("eta0*sqrt(g/depth)*cos(k*x[0]-sqrt(g*depth)*k*t)", "0"), eta0=params["eta0"], g=g, depth=depth, t=t, k=params["k"]) bc_contr = - depth * dot(ufl, n) * q * ds(1) # The dirichlet boundary condition on the right hand side bc_contr -= depth * dot(ufl, n) * q * ds(2) # We enforce a no-normal flow on the sides by removing the surface integral. # bc_contr -= dot(u_mid, n) * q * ds(3) elif bctype == 'flather': if steady_state: raise ValueError, "Can not use a time dependent boundary condition for a steady state simulation" # The Flather boundary condition on the left hand side ufl = Expression(("2*eta0*sqrt(g/depth)*cos(-sqrt(g*depth)*k*t)", "0"), eta0=params["eta0"], g=g, depth=depth, t=t, k=params["k"]) bc_contr = - depth * dot(ufl, n) * q * ds(1) Ct_mid += sqrt(g*depth)*inner(h_mid, q)*ds(1) # The contributions of the Flather boundary condition on the right hand side Ct_mid += sqrt(g*depth)*inner(h_mid, q)*ds(2) elif bctype == 'strong_dirichlet': # Do not replace anything in the surface integrals as the strong Dirichlet Boundary condition will do that bc_contr = -depth * dot(u_mid, n) * q * ds(1) bc_contr -= depth * dot(u_mid, n) * q * ds(2) if not free_slip_on_sides: bc_contr -= depth * dot(u_mid, n) * q * ds(3) else: info_red("Unknown boundary condition type: %s" % bctype) sys.exit(1) # Pressure gradient operator C_mid = g * inner(v, grad(h_mid)) * dx #+inner(avg(v),jump(h_mid,n))*dS # This term is only needed for dg element pairs # Bottom friction class FrictionExpr(Expression): def eval(self, value, x): value[0] = params["friction"] friction = FrictionExpr() if turbine_field: if type(turbine_field) == list: tf = Function(turbine_field[0], name="turbine_friction", annotate=annotate) else: tf = Function(turbine_field, name="turbine_friction", annotate=annotate) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print "Adding thrust force" # Compute the upstream velocities if implicit_turbine_thrust_parametrisation: up_u_eq = upstream_u_implicit_equation(config, tf, u, up_u, o, up_u_adv, o_adv) elif turbine_thrust_parametrisation: up_u_eq = upstream_u_equation(config, tf, u, up_u, o) def thrust_force(up_u, min=smooth_uflmin): ''' Returns the thrust force for a given upstream velcocity ''' # Now apply a pointwise transformation based on the interpolation of a loopup table c_T_coeffs = [0.08344535, -1.42428216, 9.13153605, -26.19370168, 28.8752054] c_T_coeffs.reverse() c_T = min(0.88, sum([c_T_coeffs[i]*up_u**i for i in range(len(c_T_coeffs))])) # The amount of forcing we want to apply turbine_radius = 15. A_c = pi*Constant(turbine_radius**2) # Turbine cross section f = 0.5*c_T*up_u**2*A_c return f # Apply the force in the opposite direction of the flow f_dir = -thrust_force(up_u)*u/norm_approx(u, alpha=1e-6) # Distribute this force over the turbine area thrust = inner(f_dir*tf/(Constant(config.turbine_cache.turbine_integral())*config.params["depth"]), v)*dx # Friction term # With Newton we can simply use a non-linear form if quadratic_friction and newton_solver: R_mid = friction / depth * dot(u_mid, u_mid)**0.5 * inner(u_mid, v)*dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid, u_mid)**0.5 * inner(u_mid, v)*config.site_dx(1) # With a picard iteration we need to linearise using the best guess elif quadratic_friction and not newton_solver: R_mid = friction / depth * dot(u_mid_nl, u_mid_nl)**0.5 * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * dot(u_mid_nl, u_mid_nl)**0.5 * inner(u_mid, v)*config.site_dx(1) # Use a linear drag else: R_mid = friction / depth * inner(u_mid, v) * dx if turbine_field and not (turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation): R_mid += tf / depth * inner(u_mid, v)*config.site_dx(1) # Advection term # With a newton solver we can simply use a quadratic form if include_advection and newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid), v)*dx # With a picard iteration we need to linearise using the best guess if include_advection and not newton_solver: Ad_mid = inner(dot(grad(u_mid), u_mid_nl), v)*dx if include_diffusion: # Check that we are not using a DG velocity function space, as the facet integrals are not implemented. if "Discontinuous" in str(function_space.split()[0]): raise NotImplementedError, "The diffusion term for discontinuous elements is not implemented yet." D_mid = diffusion_coef*inner(grad(u_mid), grad(v))*dx # Create the final form G_mid = C_mid + Ct_mid + R_mid # Add the advection term if include_advection: G_mid += Ad_mid # Add the diffusion term if include_diffusion: G_mid += D_mid # Add the source term if u_source: G_mid -= inner(u_source, v)*dx F = dt * G_mid - dt * bc_contr # Add the time term if not steady_state: F += M - M0 if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: F += up_u_eq if turbine_field: F -= thrust # Preassemble the lhs if possible use_lu_solver = (linear_solver == "lu") if not quadratic_friction and not include_advection: lhs_preass = assemble(dolfin.lhs(F)) # Precompute the LU factorisation if use_lu_solver: info("Computing the LU factorisation for later use ...") if bctype == 'strong_dirichlet': raise NotImplementedError, "Strong boundary condition and reusing LU factorisation is currently not implemented" lu_solver = LUSolver(lhs_preass) lu_solver.parameters["reuse_factorization"] = True solver_parameters = {"linear_solver": linear_solver, "preconditioner": preconditioner} # Do some parameter checking: if "dynamic_turbine_friction" in params["controls"]: if len(config.params["turbine_friction"]) != (params["finish_time"]-t)/dt+1: print "You control the turbine friction dynamically, but your turbine friction parameter is not an array of length 'number of timesteps' (here: %i)." % ((params["finish_time"]-t)/dt+1) import sys; sys.exit() ############################### Perform the simulation ########################### if params["dump_period"] > 0: writer = helpers.StateWriter(config) print0("Writing state to disk...") writer.write(state) step = 0 if functional is not None: if steady_state or functional_final_time_only: j = 0. else: quad = 0.5 j = dt * quad * assemble(functional.Jt(state, tf)) print0("Starting time loop...") adjointer.time.start(t) timestep = 0 while (t < params["finish_time"]): timestep += 1 t += dt params["current_time"] = t # Update bc's if bctype == "strong_dirichlet": strong_bc.update_time(t) else: ufl.t=t-(1.0-theta)*dt # Update source term if u_source: u_source.t = t-(1.0-theta)*dt step+=1 # Solve non-linear system with a Newton sovler if is_nonlinear and newton_solver: # Use a Newton solver to solve the nonlinear problem. #solver_parameters["linear_solver"] = "gmres" #solver_parameters["linear_solver"] = "superlu_dist" #solver_parameters["preconditioner"] = "ilu" # does not work in parallel #solver_parameters["preconditioner"] = "amg" #solver_parameters["linear_solver"] = "mumps" solver_parameters["newton_solver"] = {} #solver_parameters["newton_solver"]["error_on_nonconvergence"] = False solver_parameters["newton_solver"]["maximum_iterations"] = 20 solver_parameters["newton_solver"]["convergence_criterion"] = "incremental" solver_parameters["newton_solver"]["relative_tolerance"] = 1e-16 if bctype == 'strong_dirichlet': solver_benchmark.solve(F == 0, state_new, bcs = strong_bc.bcs, solver_parameters = solver_parameters, annotate=annotate, benchmark = run_benchmark, solve = solve, solver_exclude = solver_exclude) else: solver_benchmark.solve(F == 0, state_new, solver_parameters = solver_parameters, annotate=annotate, benchmark = run_benchmark, solve = solve, solver_exclude = solver_exclude) if turbine_thrust_parametrisation or implicit_turbine_thrust_parametrisation: print "Inflow velocity: ", u[0]((10, 160)) print "Estimated upstream velocity: ", up_u((640./3, 160)) print "Expected thrust force: ", thrust_force(u[0]((10, 160)), min=min)((0)) print "Total amount of thurst force applied: ", assemble(inner(Constant(1), thrust_force(up_u)*tf/config.turbine_cache.turbine_integral())*dx) us.append(u[0]((10, 160))) thrusts.append(thrust_force(u[0]((10, 160)))((0))) thrusts_est.append(assemble(inner(Constant(1), thrust_force(up_u)*tf/config.turbine_cache.turbine_integral())*dx)) import matplotlib.pyplot as plt plt.clf() plt.plot(us, thrusts, label="Analytical") plt.plot(us, thrusts_est, label="Approximated") plt.legend(loc=2) plt.savefig("thrust_plot.pdf", format='pdf') # Solve non-linear system with a Picard iteration elif is_nonlinear: # Solve the problem using a picard iteration iter_counter = 0 while True: if bctype == 'strong_dirichlet': solver_benchmark.solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, bcs = strong_bc.bcs, solver_parameters = solver_parameters, annotate=annotate, benchmark = run_benchmark, solve = solve, solver_exclude = solver_exclude) else: solver_benchmark.solve(dolfin.lhs(F) == dolfin.rhs(F), state_new, solver_parameters = solver_parameters, annotate=annotate, benchmark = run_benchmark, solve = solve, solver_exclude = solver_exclude) iter_counter += 1 if iter_counter > 0: relative_diff = abs(assemble( inner(state_new-state_nl, state_new-state_nl) * dx ))/assemble( inner(state_new, state_new) * dx ) info_blue("Picard iteration " + str(iter_counter) + " relative difference: " + str(relative_diff)) if relative_diff < picard_relative_tolerance: info("Picard iteration converged after " + str(iter_counter) + " iterations.") break elif iter_counter >= picard_iterations: info_red("Picard iteration reached maximum number of iterations (" + str(picard_iterations) + ") with a relative difference of " + str(relative_diff) + ".") break state_nl.assign(state_new) # Solve linear system with preassembled matrices else: rhs_preass = assemble(dolfin.rhs(F)) # Apply dirichlet boundary conditions if bctype == 'strong_dirichlet': [bc.apply(lhs_preass, rhs_preass) for bc in strong_bc.bcs] if use_lu_solver: info("Using a LU solver to solve the linear system.") lu_solver.solve(state.vector(), rhs_preass, annotate=annotate) else: solver_benchmark.solve(lhs_preass, state_new.vector(), rhs_preass, solver_parameters["linear_solver"], solver_parameters["preconditioner"], annotate=annotate, benchmark = run_benchmark, solve = solve, solver_exclude = solver_exclude) # After the timestep solve, update state state.assign(state_new) # Set the control function for the upcoming timestep. if turbine_field: if type(turbine_field) == list: tf.assign(turbine_field[timestep]) else: tf.assign(turbine_field) if params["dump_period"] > 0 and step%params["dump_period"] == 0: print0("Writing state to disk...") writer.write(state) if functional is not None: if not (functional_final_time_only and t < params["finish_time"]): if steady_state or functional_final_time_only: quad = 1.0 elif t >= params["finish_time"]: quad = 0.5 * dt else: quad = 1.0 * dt j += quad * assemble(functional.Jt(state, tf)) # Increase the adjoint timestep adj_inc_timestep(time=t, finished = not t < params["finish_time"]) print0("New timestep t = %f" % t) print0("Ending time loop.") if functional is not None: return j
def load_checkpoint(self, filename): try: self.memo = cPickle.load(open(filename, "rb")) except IOError: info_red("Warning: Checkpoint file '%s' not found." % filename)