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 solve(self): # Fetch the function spaces V, Q = self.problem.parameters.discretisation # Set up the velocity functions u = TrialFunction(V) v = TestFunction(V) u0 = Function(V) u1 = Function(V) # Set up the pressure functions p = TrialFunction(Q) q = TestFunction(Q) p1 = Function(Q) # Define coefficients k = Constant(self.problem.parameters.dt) f = Constant((0,0)) nu = Constant(self.problem.parameters.viscosity) # Tentative velocity step F1 = (1/k) * inner(u-u0, v)*dx \ + inner(grad(u0)*u0, v)*dx \ + nu * inner(grad(u), grad(v))*dx \ - inner(f, v)*dx a1 = lhs(F1) L1 = rhs(F1) A1 = assemble(a1) # Pressure update a2 = inner(grad(p), grad(q))*dx L2 = (-1./k) * div(u1) * q * dx A2 = assemble(a2) # Velocity update a3 = inner(u, v)*dx L3 = inner(u1, v)*dx - k * inner(grad(p1), v)*dx A3 = assemble(a3) # Iterate over timesteps, start at dt self.t = self.problem.parameters.dt while self.t < self.problem.parameters.finish_time: print 'Starting timestep at time = ', self.t # Update the boundary conditions for the current timestep self.bcs.update_time(t=self.t) # Tentative velocity step print 'Step 1: Computing tentative velocity' b1 = assemble(L1) [bc.apply(A1, b1) for bc in self.bcs.bc_u] solve(A1, u1.vector(), b1, "gmres", "default") end() # Pressure update print 'Step 2: Updating pressure' b2 = assemble(L2) [bc.apply(A2, b2) for bc in self.bcs.bc_p] solve(A2, p1.vector(), b2, "gmres", self.parameters.prec) end() # Velocity update print 'Step 3: Updating velocity' b3 = assemble(L3) [bc.apply(A3, b3) for bc in self.bcs.bc_u] solve(A3, u1.vector(), b3, "gmres", "default") end() # Timestep update print 'Step 4: Dump and update timestep' if (self.t/self.problem.parameters.dt)%self.parameters.dump_period == 0: writer = StateWriter(solver=self) writer.write(u1, p1) u0.assign(u1) self.t += self.problem.parameters.dt if self.parameters.live_plotting: plot(p1, title="Pressure", rescale=True) plot(u1, title="Velocity", rescale=True) if self.parameters.live_plotting: interactive()
def solve(self): # Fetch the function spaces V, Q = self.problem.parameters.discretisation # Set up the velocity functions u = TrialFunction(V) v = TestFunction(V) u0 = Function(V) u1 = Function(V) # Set up the pressure functions p = TrialFunction(Q) q = TestFunction(Q) p1 = Function(Q) # Define coefficients k = Constant(self.problem.parameters.dt) f = Constant((0, 0)) nu = Constant(self.problem.parameters.viscosity) # Tentative velocity step F1 = (1/k) * inner(u-u0, v)*dx \ + inner(grad(u0)*u0, v)*dx \ + nu * inner(grad(u), grad(v))*dx \ - inner(f, v)*dx a1 = lhs(F1) L1 = rhs(F1) A1 = assemble(a1) # Pressure update a2 = inner(grad(p), grad(q)) * dx L2 = (-1. / k) * div(u1) * q * dx A2 = assemble(a2) # Velocity update a3 = inner(u, v) * dx L3 = inner(u1, v) * dx - k * inner(grad(p1), v) * dx A3 = assemble(a3) # Iterate over timesteps, start at dt self.t = self.problem.parameters.dt while self.t < self.problem.parameters.finish_time: print 'Starting timestep at time = ', self.t # Update the boundary conditions for the current timestep self.bcs.update_time(t=self.t) # Tentative velocity step print 'Step 1: Computing tentative velocity' b1 = assemble(L1) [bc.apply(A1, b1) for bc in self.bcs.bc_u] solve(A1, u1.vector(), b1, "gmres", "default") end() # Pressure update print 'Step 2: Updating pressure' b2 = assemble(L2) [bc.apply(A2, b2) for bc in self.bcs.bc_p] solve(A2, p1.vector(), b2, "gmres", self.parameters.prec) end() # Velocity update print 'Step 3: Updating velocity' b3 = assemble(L3) [bc.apply(A3, b3) for bc in self.bcs.bc_u] solve(A3, u1.vector(), b3, "gmres", "default") end() # Timestep update print 'Step 4: Dump and update timestep' if (self.t / self.problem.parameters.dt ) % self.parameters.dump_period == 0: writer = StateWriter(solver=self) writer.write(u1, p1) u0.assign(u1) self.t += self.problem.parameters.dt if self.parameters.live_plotting: plot(p1, title="Pressure", rescale=True) plot(u1, title="Velocity", rescale=True) if self.parameters.live_plotting: interactive()