diff --git a/examples/07b_calc_aep_from_rose_use_class.py b/examples/07b_calc_aep_from_rose_use_class.py new file mode 100644 index 000000000..358fbc19e --- /dev/null +++ b/examples/07b_calc_aep_from_rose_use_class.py @@ -0,0 +1,74 @@ +# Copyright 2022 NREL + +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# See https://floris.readthedocs.io for documentation + + +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from scipy.interpolate import NearestNDInterpolator +from floris.tools import FlorisInterface, WindRose, wind_rose + +""" +This example demonstrates how to calculate the Annual Energy Production (AEP) +of a wind farm using wind rose information stored in a .csv file. + +The wind rose information is first loaded, after which we initialize our Floris +Interface. A 3 turbine farm is generated, and then the turbine wakes and powers +are calculated across all the wind directions. Finally, the farm power is +converted to AEP and reported out. +""" + +# Read in the wind rose using the class +wind_rose = WindRose() +wind_rose.read_wind_rose_csv("inputs/wind_rose.csv") + +# Show the wind rose +wind_rose.plot_wind_rose() + +# Load the FLORIS object +fi = FlorisInterface("inputs/gch.yaml") # GCH model +# fi = FlorisInterface("inputs/cc.yaml") # CumulativeCurl model + +# Assume a three-turbine wind farm with 5D spacing. We reinitialize the +# floris object and assign the layout, wind speed and wind direction arrays. +D = 126.0 # Rotor diameter for the NREL 5 MW +fi.reinitialize( + layout=[[0.0, 5* D, 10 * D], [0.0, 0.0, 0.0]] +) + +# Compute the AEP using the default settings +aep = fi.get_farm_AEP_wind_rose_class(wind_rose=wind_rose) +print("Farm AEP (default options): {:.3f} GWh".format(aep / 1.0e9)) + +# Compute the AEP again while specifying a cut-in and cut-out wind speed. +# The wake calculations are skipped for any wind speed below respectively +# above the cut-in and cut-out wind speed. This can speed up computation and +# prevent unexpected behavior for zero/negative and very high wind speeds. +# In this example, the results should not change between this and the default +# call to 'get_farm_AEP()'. +aep = fi.get_farm_AEP_wind_rose_class( + wind_rose=wind_rose, + cut_in_wind_speed=3.0, # Wakes are not evaluated below this wind speed + cut_out_wind_speed=25.0, # Wakes are not evaluated above this wind speed +) +print("Farm AEP (with cut_in/out specified): {:.3f} GWh".format(aep / 1.0e9)) + +# Finally, we can also compute the AEP while ignoring all wake calculations. +# This can be useful to quantity the annual wake losses in the farm. Such +# calculations can be facilitated by enabling the 'no_wake' handle. +aep_no_wake = fi.get_farm_AEP_wind_rose_class(wind_rose=wind_rose, no_wake=True) +print("Farm AEP (no_wake=True): {:.3f} GWh".format(aep_no_wake / 1.0e9)) + + +plt.show() \ No newline at end of file diff --git a/floris/tools/optimization/layout_optimization/layout_optimization_boundary_grid.py b/floris/tools/optimization/layout_optimization/layout_optimization_boundary_grid.py index dc2a50e8e..a5bc900e4 100644 --- a/floris/tools/optimization/layout_optimization/layout_optimization_boundary_grid.py +++ b/floris/tools/optimization/layout_optimization/layout_optimization_boundary_grid.py @@ -42,7 +42,6 @@ def __init__( boundary_spacing=None, ): self.fi = fi - self.boundary_x = np.array([val[0] for val in boundaries]) self.boundary_y = np.array([val[1] for val in boundaries]) boundary = np.zeros((len(self.boundary_x), 2)) @@ -224,7 +223,6 @@ def _discrete_grid( d = np.array([i for x in xlocs for i in row_number]) layout_x = np.array([x for x in xlocs for y in ylocs]) + d*y_spacing*np.tan(shear) layout_y = np.array([y for x in xlocs for y in ylocs]) - # rotate rotate_x = np.cos(rotation)*layout_x - np.sin(rotation)*layout_y rotate_y = np.sin(rotation)*layout_x + np.cos(rotation)*layout_y @@ -243,7 +241,6 @@ def _discrete_grid( # arrange final x,y points return_x = rotate_x[meets_constraints] return_y = rotate_y[meets_constraints] - return return_x, return_y def find_lengths(self, x, y, npoints): @@ -327,7 +324,6 @@ def _place_boundary_turbines(self, start, boundary_poly, nturbs=None, spacing=No nBounds = len(xBounds) lenBound = self.find_lengths(xBounds, yBounds, len(xBounds) - 1) circumference = sum(lenBound) - if nturbs is not None and spacing is None: # When the number of boundary turbines is specified nturbs = int(nturbs) diff --git a/floris/tools/optimization/layout_optimization/layout_optimization_pyoptsparse_spread.py b/floris/tools/optimization/layout_optimization/layout_optimization_pyoptsparse_spread.py index 9b04cb2d9..19648bd21 100644 --- a/floris/tools/optimization/layout_optimization/layout_optimization_pyoptsparse_spread.py +++ b/floris/tools/optimization/layout_optimization/layout_optimization_pyoptsparse_spread.py @@ -226,5 +226,4 @@ def plot_layout_opt_results(self): plt.plot( [verts[i][0], verts[i + 1][0]], [verts[i][1], verts[i + 1][1]], "b" ) - plt.show() diff --git a/floris/tools/optimization/legacy/pyoptsparse/optimization.py b/floris/tools/optimization/legacy/pyoptsparse/optimization.py index 65e6c2a49..b9ae3239c 100644 --- a/floris/tools/optimization/legacy/pyoptsparse/optimization.py +++ b/floris/tools/optimization/legacy/pyoptsparse/optimization.py @@ -27,7 +27,14 @@ class Optimization(LoggerBase): Optimization: An instantiated Optimization object. """ - def __init__(self, model, solver=None): + def __init__( + self, + model, + solver=None, + storeHistory='hist.hist', + optOptions=None, + timeLimit=None + ): """ Instantiate Optimization object and its parameters. """ @@ -51,7 +58,11 @@ def __init__(self, model, solver=None): + str(self.solver_choices) ) - self.reinitialize(solver=solver) + self.optOptions = optOptions + self.reinitialize(solver=solver, optOptions=optOptions) + self.storeHistory = storeHistory + self.timeLimit = timeLimit + # Private methods @@ -85,7 +96,7 @@ def _reinitialize(self, solver=None, optOptions=None): if self.solver == "SNOPT": self.optOptions = {"Major optimality tolerance": 1e-7} else: - self.optOptions = {} + self.optOptions = {"IPRINT": 0} exec("self.opt = pyoptsparse." + self.solver + "(options=self.optOptions)") @@ -93,12 +104,19 @@ def _optimize(self): if hasattr(self.model, "_sens"): self.sol = self.opt(self.optProb, sens=self.model._sens) else: - self.sol = self.opt(self.optProb, sens="CDR", storeHistory='hist.hist') + if self.timeLimit is not None: #sens="CDR" + self.sol = self.opt( + self.optProb, + storeHistory=self.storeHistory, + timeLimit=self.timeLimit + ) + else: + self.sol = self.opt(self.optProb, storeHistory=self.storeHistory) # Public methods - def reinitialize(self, solver=None): - self._reinitialize(solver=solver) + def reinitialize(self, solver=None, optOptions=None): + self._reinitialize(solver=solver, optOptions=optOptions) def optimize(self): self._optimize() diff --git a/floris/tools/wind_rose.py b/floris/tools/wind_rose.py index 94951e381..ff8e68c61 100644 --- a/floris/tools/wind_rose.py +++ b/floris/tools/wind_rose.py @@ -701,6 +701,7 @@ def make_wind_rose_from_user_data( return self.df + def read_wind_rose_csv( self, filename diff --git a/floris/turbine_library/nrel_5MW_cc.yaml b/floris/turbine_library/nrel_5MW_cc.yaml new file mode 100644 index 000000000..242ed21ae --- /dev/null +++ b/floris/turbine_library/nrel_5MW_cc.yaml @@ -0,0 +1,164 @@ +turbine_type: 'nrel_5MW' +generator_efficiency: 1.0 +hub_height: 90.0 +pP: 1.88 +pT: 1.88 +rotor_diameter: 126.0 +TSR: 8.0 +power_thrust_table: + power: + - 0.0 + - 0.000000 + - 0.000000 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.43 + - 0.0 + - 0.0 + thrust: + - 0.0 + - 0.0 + - 0.0 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.75 + - 0.0 + - 0.0 + wind_speed: + - 0.0 + - 2.0 + - 2.5 + - 3.0 + - 3.5 + - 4.0 + - 4.5 + - 5.0 + - 5.5 + - 6.0 + - 6.5 + - 7.0 + - 7.5 + - 8.0 + - 8.5 + - 9.0 + - 9.5 + - 10.0 + - 10.5 + - 11.0 + - 11.5 + - 12.0 + - 12.5 + - 13.0 + - 13.5 + - 14.0 + - 14.5 + - 15.0 + - 15.5 + - 16.0 + - 16.5 + - 17.0 + - 17.5 + - 18.0 + - 18.5 + - 19.0 + - 19.5 + - 20.0 + - 20.5 + - 21.0 + - 21.5 + - 22.0 + - 22.5 + - 23.0 + - 23.5 + - 24.0 + - 24.5 + - 25.0 + - 25.01 + - 25.02 + - 50.0