diff --git a/Examples/example_12.py b/Examples/example_12.py
index 21fe35a6..f6468947 100644
--- a/Examples/example_12.py
+++ b/Examples/example_12.py
@@ -21,24 +21,20 @@
from ROSCO_toolbox import turbine as ROSCO_turbine
from ROSCO_toolbox import controller as ROSCO_controller
+
def run_example():
# Shorthand directories
this_dir = os.path.dirname(os.path.abspath(__file__))
tune_dir = os.path.join(this_dir, '../Tune_Cases')
test_dir = os.path.join(this_dir, '../Test_Cases')
- # Setup linear turbine paths
- linfile_root = os.path.join(test_dir, 'IEA-15-240-RWT-UMaineSemi', 'linearizations')
- load_parallel = True
- linturb_options = {'linfile_root': linfile_root,
- 'load_parallel': load_parallel}
-
# ROSCO options
- parameter_filename = os.path.join(tune_dir, 'IEA15MW.yaml')
+ parameter_filename = os.path.join(tune_dir, 'IEA15MW_robust.yaml')
inps = load_rosco_yaml(parameter_filename)
path_params = inps['path_params']
turbine_params = inps['turbine_params']
controller_params = inps['controller_params']
+ linmodel_tuning = inps['linmodel_tuning']
ROSCO_options = {
'path_params': path_params,
'turbine_params': turbine_params,
@@ -46,7 +42,7 @@ def run_example():
}
# Path options
- example_out_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'examples_out' )
+ example_out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'examples_out')
output_name = '12_robust_scheduling'
path_options = {'output_dir': example_out_dir,
'output_name': output_name
@@ -65,18 +61,19 @@ def run_example():
k_float = controller.Kp_float
# Scheduling options
- opt_options = { 'driver': 'optimization', #'design_of_experiments',
- 'windspeed': [12, 13, 14, 15, 18, 24],
- 'stability_margin': 0.1,
- 'omega':[0.05, 0.2], # two inputs denotes a range for a design variable
- 'k_float': [k_float]} # one input denotes a set value
+ opt_options = {'driver': 'optimization', # 'design_of_experiments',
+ 'windspeed': controller_params['U_pc'],
+ 'stability_margin': linmodel_tuning['stability_margin'],
+ 'omega': [linmodel_tuning['omega_pc']['min'],
+ linmodel_tuning['omega_pc']['max']], # two inputs denotes a range for a design variable
+ 'k_float': [k_float]} # one input denotes a set value
# Collect options
options = {}
- options['linturb_options'] = linturb_options
- options['ROSCO_options'] = ROSCO_options
- options['path_options'] = path_options
- options['opt_options'] = opt_options
+ options['linturb_options'] = linmodel_tuning
+ options['ROSCO_options'] = ROSCO_options
+ options['path_options'] = path_options
+ options['opt_options'] = opt_options
# Run robust scheduling
sd = rsched_driver(options)
@@ -84,12 +81,11 @@ def run_example():
sd.execute()
# Re-define ROSCO tuning parameters
- controller.U_pc = opt_options['windspeed']
controller.omega_pc = sd.omegas
controller.zeta_pc = np.ones(
len(opt_options['windspeed'])) * controller.zeta_pc
- # Tune ROSCO with to satisfy robust stability margin
+ # Tune ROSCO with to satisfy robust stability margin
controller.tune_controller(turbine)
# Plot gain schedule
@@ -118,10 +114,10 @@ def run_example():
ax[4].set_ylabel('Integral Gain')
ax[4].grid()
-
- if False:
+ if True:
plt.show()
else:
plt.savefig(os.path.join(example_out_dir, '12_RobustSched.png'))
+
if __name__ == '__main__':
run_example()
diff --git a/README.md b/README.md
index 7393a7a0..6e47c8b6 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,31 @@
# NREL's Reference OpenSource Controller (ROSCO) toolbox for wind turbine applications
-NREL's Reference OpenSource Controller (ROSCO) toolbox for wind turbine applications is a toolbox designed to ease controller implementation for the wind turbine researcher. Some primary capabilities include:
+NREL's Reference OpenSource Controller (ROSCO) for wind turbine applications is a toolset designed to ease controller use and implementation for the wind turbine researcher. Some primary capabilities include:
+* A reference controller with industry-standard functionality
* Generic tuning of NREL's ROSCO controller
* Simple 1-DOF turbine simulations for quick controller capability verifications
* Parsing of OpenFAST input and output files
## Introduction
-The NREL Reference OpenSource Controller (ROSCO) provides an open, modular and fully adaptable baseline wind turbine controller to the scientific community. The ROSCO toolbox leverages this architecture and implementation to provide a generic tuning process for the controller. Because of the open character and modular set-up, scientists are able to collaborate and contribute in making continuous improvements to the code for the controller and the toolbox. The ROSCO toolbox is a mostly-python code base with a number of functionalities.
+The NREL Reference OpenSource Controller (ROSCO) provides an open, modular and fully adaptable baseline wind turbine controller to the scientific community. The ROSCO toolbox leverages this architecture and implementation to provide a generic tuning process for the controller. Because of the open character and modular set-up, scientists are able to collaborate and contribute in making continuous improvements to the code for the controller and the toolbox. The ROSCO controller is implemented in FORTRAN, while the remainder of the toolset is a mostly-python code base with a number of functionalities.
-* [ROSCO](https://github.com/NREL/ROSCO) - the fortran source code for the ROSCO controller.
-* [Examples](https://github.com/NREL/ROSCO_toolbox/tree/master/examples) - short working examples of the capabilities of the ROSCO toolbox.
-* [Tune_Cases](https://github.com/NREL/ROSCO_toolbox/tree/master/Tune_Cases) - example generic tuning scripts for a number of open-source reference turbines.
-* [Test_Cases](https://github.com/NREL/ROSCO_toolbox/tree/master/Test_Cases) - numerous NREL 5MW bases cases to run for controller updates and comparisons. A "test-suite", if you will...
-* [Matlab_Toolbox](https://github.com/NREL/ROSCO_toolbox/tree/master/Matlab_Toolbox) - MATLAB scripts to parse and plot simulation output data.
-* [ofTools](https://github.com/NREL/ROSCO_toolbox/tree/master/ofTools) - A number of scripts to facilitate usage of OpenFAST and manage OpenFAST input and output files.
+* [ROSCO](https://github.com/NREL/ROSCO/tree/main/ROSCO) - the fortran source code for the ROSCO controller.
+* [Examples](https://github.com/NREL/ROSCO/tree/main/Examples) - short working examples of the capabilities of the ROSCO toolbox.
+* [Tune_Cases](https://github.com/NREL/ROSCO/tree/main/Tune_Cases) - example generic tuning scripts for a number of open-source reference turbines.
+* [Test_Cases](https://github.com/NREL/ROSCO/tree/main/Test_Cases) - numerous NREL 5MW bases cases to run for controller updates and comparisons. A "test-suite", if you will...
+* [Matlab_Toolbox](https://github.com/NREL/ROSCO/tree/main/Matlab_Toolbox) - MATLAB scripts to parse and plot simulation output data.
+* [ofTools](https://github.com/NREL/ROSCO/tree/main/ROSCO_toolbox/ofTools) - A number of scripts to facilitate usage of OpenFAST and manage OpenFAST input and output files.
+* [linear](https://github.com/NREL/ROSCO/tree/main/ROSCO_toolbox/linear) - Scripts to aid with the use of linear models for controller tuning and simplified simulation.
## Documentation
-All relevant documentation about the ROSCO toolbox and ROSCO controller can be found at through [ROSCO's readthedocs webpage](https://rosco-toolbox.readthedocs.io/en/latest/). Here, users can find the information on [installing the ROSCO toolbox](https://rosco-toolbox.readthedocs.io/en/latest/source/install.html#installing-the-rosco-toolbox) and [compiling ROSCO](https://rosco-toolbox.readthedocs.io/en/latest/source/install.html#compiling-rosco) for control purposes. Additionally, there is information on the standard workflow and uses cases for the ROSCO toolchain, and more.
+All relevant documentation about the ROSCO toolbox and ROSCO controller can be found at through [ROSCO's readthedocs webpage](https://rosco.readthedocs.io/en/latest/). Here, users can find the information on [installing the ROSCO tools](https://rosco.readthedocs.io/en/latest/source/install.html) and [compiling ROSCO](https://rosco.readthedocs.io/en/latest/source/install.html#rosco-controller) for control purposes. Additionally, there is information on the standard workflow, uses cases for the ROSCO tool-chain, and more.
+
+## Issues and Discussion
+If you find issues with any of the code that resides in this repository, it is encouraged for you to open a [GitHub issue](https://github.com/NREL/ROSCO/issues). If you have general questions or comments regarding the code, please start a [discussion via GitHub](https://github.com/NREL/ROSCO/discussions). We encourage you to use these resources for all ROSCO-related questions and comments, rather than other resources such as the FAST forums. This helps us keep ROSCO-related items centralized, and provides a singular place for the community to look when they have questions that might arise. Please keep in mind that we will do our very best to respond in a timely manner, but may take a few days to get back to you if you catch us during a busy time.
+
+## Contributing
+If it wasn't obvious from _open-source_ being in the title of the tool-set, this is an open-source code base that we would love for the community to contribute to. If you find yourself fixing any bugs, writing new routines, or even making small typo changes, please submit a [pull request](https://github.com/NREL/ROSCO/pulls).
## Survey
Please help us better understand the ROSCO user-base and how we can improve rosco through this brief survey:
@@ -27,18 +35,18 @@ Please help us better understand the ROSCO user-base and how we can improve rosc
If the ROSCO Toolbox played a role in your research, please cite it. This software can be
cited as:
- NREL: ROSCO Toolbox. Version 2.3.0, https://github.com/NREL/rosco_toolbox, 2021.
+ NREL: ROSCO. Version 2.4.0, https://github.com/NREL/ROSCO, 2021.
For LaTeX users:
```
@misc{ROSCO_toolbox_2021,
author = {NREL},
- title = {{ROSCO Toolbox. Version 2.3.0}},
+ title = {{ROSCO. Version 2.4.0}},
year = {2021},
publisher = {GitHub},
journal = {GitHub repository},
- url = {https://github.com/NREL/rosco_toolbox}
+ url = {https://github.com/NREL/ROSCO}
}
```
If the ROSCO generic tuning theory and implementation played a roll in your research, please cite the following paper
@@ -54,10 +62,6 @@ URL = {https://wes.copernicus.org/preprints/wes-2021-19/},
DOI = {10.5194/wes-2021-19}
}
```
-Additionally, if you have extensively used the [ROSCO](https://github.com/NREL/ROSCO) controller or [WISDEM](https://github.com/wisdem/wisdem), please cite them accordingly.
-
## Additional Contributors and Acknowledgments
-Primary contributions to the ROSCO Toolbox have been provided by researchers the National Renewable Energy Laboratory (Nikhar J. Abbas, Daniel Zalkind, Alan Wright, and Paul Fleming) and the University of Colorado Boulder (Lucy Pao). Much of the intellect behind these contributions has been inspired or derived from an extensive amount of work in the literature. The bulk of this has been cited through the primary publications about this work.
-
-There have been a number of contributors to the logic of the ROSCO controller itself. Please see the [ROSCO github page](https://github.com/NREL/ROSCO) for more information on who these contributors have been.
+Primary contributions to ROSCO have been provided by researchers the National Renewable Energy Laboratory and the University of Colorado Boulder. Additionally, the ROSCO controller was built upon the foundations of the [Delft Research Controller](https://github.com/TUDelft-DataDrivenControl/DRC_Fortran). Much of the intellect behind these contributions has been inspired or derived from an extensive amount of work in the literature. The bulk of this has been cited through the primary publications about this work.
diff --git a/ROSCO/CMakeLists.txt b/ROSCO/CMakeLists.txt
index f3a14867..a14c57b8 100644
--- a/ROSCO/CMakeLists.txt
+++ b/ROSCO/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.6)
-project(ROSCO VERSION 2.3.0 LANGUAGES Fortran)
+project(ROSCO VERSION 2.4.0 LANGUAGES Fortran)
set(CMAKE_Fortran_MODULE_DIRECTORY "${CMAKE_BINARY_DIR}/ftnmods")
diff --git a/ROSCO/src/Constants.f90 b/ROSCO/src/Constants.f90
index 83ab40d7..ddcd070c 100644
--- a/ROSCO/src/Constants.f90
+++ b/ROSCO/src/Constants.f90
@@ -10,7 +10,7 @@
! specific language governing permissions and limitations under the License.
MODULE Constants
- Character(*), PARAMETER :: rosco_version = 'v2.3.0' ! ROSCO version
+ Character(*), PARAMETER :: rosco_version = 'v2.4.0' ! ROSCO version
REAL(8), PARAMETER :: RPS2RPM = 9.5492966 ! Factor to convert radians per second to revolutions per minute.
REAL(8), PARAMETER :: R2D = 57.295780 ! Factor to convert radians to degrees.
REAL(8), PARAMETER :: D2R = 0.0175 ! Factor to convert degrees to radians.
diff --git a/ROSCO_toolbox/__init__.py b/ROSCO_toolbox/__init__.py
index 5f3415c4..e290adc2 100644
--- a/ROSCO_toolbox/__init__.py
+++ b/ROSCO_toolbox/__init__.py
@@ -3,4 +3,4 @@
__author__ = """Nikhar J. Abbas and Daniel S. Zalkind"""
__email__ = 'nikhar.abbas@nrel.gov'
-__version__ = '2.3.0'
\ No newline at end of file
+__version__ = '2.4.0'
\ No newline at end of file
diff --git a/ROSCO_toolbox/controller.py b/ROSCO_toolbox/controller.py
index 5f0cbe1f..83d1107c 100644
--- a/ROSCO_toolbox/controller.py
+++ b/ROSCO_toolbox/controller.py
@@ -13,6 +13,7 @@
import sys
import datetime
from scipy import interpolate, gradient, integrate
+from ROSCO_toolbox.utilities import list_check
# Some useful constants
now = datetime.datetime.now()
@@ -63,11 +64,12 @@ def __init__(self, controller_params):
self.Flp_Mode = controller_params['Flp_Mode']
# Necessary parameters
- self.U_pc = controller_params['U_pc']
- self.zeta_pc = controller_params['zeta_pc']
- self.omega_pc = controller_params['omega_pc']
- self.zeta_vs = controller_params['zeta_vs']
- self.omega_vs = controller_params['omega_vs']
+ self.U_pc = list_check(controller_params['U_pc'], return_bool=False)
+ self.zeta_pc = list_check(controller_params['zeta_pc'], return_bool=False)
+ self.omega_pc = list_check(controller_params['omega_pc'], return_bool=False)
+ self.zeta_vs = controller_params['zeta_vs']
+ self.omega_vs = controller_params['omega_vs']
+ self.interp_type = controller_params['interp_type']
# Optional parameters with defaults
self.min_pitch = controller_params['min_pitch']
@@ -122,6 +124,12 @@ def __init__(self, controller_params):
if self.WS_GS_n <= self.PC_GS_n:
raise Exception('Number of WS breakpoints is not greater than pitch control breakpoints')
+ # Error checking: pitch controller inputs
+ if list_check(self.U_pc) and \
+ (list_check(self.omega_pc) or list_check(self.zeta_pc)) and \
+ not len(self.U_pc) == len(self.omega_pc) == len(self.zeta_pc):
+ raise Exception(
+ 'U_pc, omega_pc, and zeta_pc are all list-like and are not of equal length')
def tune_controller(self, turbine):
@@ -241,17 +249,18 @@ def tune_controller(self, turbine):
B_tau = B_tau * np.ones(len(v))
# Resample omega_ and zeta_pc at above rated wind speeds
- if self.U_pc \
- and isinstance(self.omega_pc, (list,np.ndarray)) \
- and isinstance(self.zeta_pc, (list,np.ndarray)) \
- and len(self.U_pc) == len(self.omega_pc) == len(self.zeta_pc):
- self.omega_pc_U = multi_sigma(v_above_rated[1:],self.U_pc,self.omega_pc)
- self.zeta_pc_U = multi_sigma(v_above_rated[1:],self.U_pc,self.zeta_pc)
- elif isinstance(self.omega_pc, float) and isinstance(self.zeta_pc, float):
+ if not list_check(self.omega_pc) and not list_check(self.zeta_pc):
self.omega_pc_U = self.omega_pc * np.ones(len(v_above_rated[1:]))
self.zeta_pc_U = self.zeta_pc * np.ones(len(v_above_rated[1:]))
else:
- raise Exception('ROSCO_toolbox: The lengths of U_pc, omega_pc, and zeta_pc must be equal')
+ if self.interp_type == 'sigma': # sigma interpolation
+ self.omega_pc_U = multi_sigma(v_above_rated[1:], self.U_pc, self.omega_pc)
+ self.zeta_pc_U = multi_sigma(v_above_rated[1:], self.U_pc, self.zeta_pc)
+ else: # standard scipy interpolation types
+ interp_omega = interpolate.interp1d(self.U_pc, self.omega_pc, kind=self.interp_type, bounds_error=False, fill_value='extrapolate')
+ interp_zeta = interpolate.interp1d(self.U_pc, self.zeta_pc, kind=self.interp_type, bounds_error=False, fill_value='extrapolate')
+ self.omega_pc_U = interp_omega(v_above_rated[1:])
+ self.zeta_pc_U = interp_zeta(v_above_rated[1:])
# -- Find gain schedule --
self.pc_gain_schedule = ControllerTypes()
diff --git a/ROSCO_toolbox/inputs/toolbox_schema.yaml b/ROSCO_toolbox/inputs/toolbox_schema.yaml
index 3dfccb2a..73e69211 100644
--- a/ROSCO_toolbox/inputs/toolbox_schema.yaml
+++ b/ROSCO_toolbox/inputs/toolbox_schema.yaml
@@ -123,7 +123,7 @@ properties:
minimum: 0
maximum: 3
default: 2
- description: Generator torque control mode in above rated conditions (0- constant torque, 1- constant power, 2- TSR tracking PI control, 3- TSR tracking and constant power)
+ description: Generator torque control mode in above rated conditions (0- constant torque, 1- constant power, 2- TSR tracking PI control with constant torque, 3- TSR tracking with constant power)
PC_ControlMode:
type: number
minimum: 0
@@ -203,6 +203,11 @@ properties:
type: number
minimum: 0
default: [0.2]
+ interp_type:
+ type: string
+ description: Type of interpolation between above rated tuning values (only used for multiple pitch controller tuning values)
+ default: linear
+ enum: ['sigma', 'linear', 'quadratic', 'cubic']
zeta_vs:
type: number
minimum: 0
@@ -305,7 +310,7 @@ properties:
minimum: 0
description: Flap controller desired natural frequency [rad/s]
unit: rad/s
-
+
filter_params:
type: object
default: {}
@@ -323,8 +328,42 @@ properties:
default: 0.01042
description: Natural frequency of first-order high-pass filter for nacelle fore-aft motion [rad/s]
- # modeling_options:
- # type: object
- # default: {}
- # properties:
-
+ linmodel_tuning:
+ type: object
+ default: {}
+ description: Inputs used for tuning ROSCO using linear (level 2) models
+ properties:
+ type:
+ type: string
+ description: Type of level 2 based tuning - robust gain scheduling (robust) or simulation based optimization (simulation)
+ default: 'none'
+ enum: ['none', 'robust', 'simulation']
+ linfile_path:
+ type: string
+ description: Path to OpenFAST linearization (.lin) files, if they exist
+ default: none
+ lintune_outpath:
+ type: string
+ description: Path for outputs from linear model based tuning
+ default: lintune_outfiles
+ load_parallel:
+ type: boolean
+ description: Load linearization files in parallel (True/False)
+ default: False
+ stability_margin:
+ type: [number, array]
+ description: Desired maximum stability margin
+ default: 0.1
+ omega_pc:
+ type: object
+ default: {}
+ description: Pitch controller bandwidth constraints
+ min:
+ type: [number, array]
+ default: 0.0
+ description: Desired maximum allowable omega for robust tuning. Array must be of length U_pc.
+ max:
+ type: [number, array]
+ default: 0.2
+ description: Desired maximum allowable omega for robust tuning. Array must be of length U_pc.
+
diff --git a/ROSCO_toolbox/linear/lin_util.py b/ROSCO_toolbox/linear/lin_util.py
index 351ddce3..29203da0 100644
--- a/ROSCO_toolbox/linear/lin_util.py
+++ b/ROSCO_toolbox/linear/lin_util.py
@@ -128,25 +128,25 @@ def sens_min(om): return -sp.signal.bode(sp_sens, w=om)[1]
i += 1
w0 = ws[i]
+ sm_mag = sp.signal.freqresp(sp_plant, w=w0)[1]
+ sm = np.sqrt((1 - np.abs(sm_mag.real))**2 + sm_mag.imag**2)
+ nearest_nyquist = nyquist_min(ws).min()
+ nearest_nyquist_freq = ws[nyquist_min(ws).argmin()]
+ mag_at_min = sp.signal.freqresp(sp_plant, w=nearest_nyquist_freq)[1]
if any(sp_sens.poles > 0):
- sm_mag = sp.signal.freqresp(sp_plant, w=w0)[1]
- sm = np.sqrt((1 - np.abs(sm_mag.real))**2 + sm_mag.imag**2)
- nearest_nyquist = nyquist_min(ws).min()
- nearest_nyquist_freq = ws[nyquist_min(ws).argmin()]
- mag_at_min = sp.signal.freqresp(sp_plant, w=nearest_nyquist_freq)[1]
if nearest_nyquist < sm:
- res = sp.optimize.minimize(nyquist_min, ws[np.argmin(nyquist_min(ws))], method='SLSQP', options={
- 'finite_diff_rel_step': 1e-6})
- sm2 = res.fun
+ res = sp.optimize.minimize(nyquist_min, nearest_nyquist_freq, method='SLSQP', options={
+ 'finite_diff_rel_step': 1e-8})
+ sm2 = min(abs(res.fun), abs(nearest_nyquist))
sm_list = [sm, sm2]
mag_list = [np.abs(sm_mag), np.abs(mag_at_min)]
sm = sm_list[np.argmax(mag_list)]
- sm *= -1 # Flip sign because it's unstable
+ sm *= -1 # Flip sign because it's unstable
else:
- res = sp.optimize.minimize(nyquist_min, ws[np.argmin(nyquist_min(ws))], method='SLSQP', options={
- 'finite_diff_rel_step': 1e-6})
- sm = res.fun
+ res = sp.optimize.minimize(nyquist_min, nearest_nyquist_freq, method='SLSQP',
+ options={'finite_diff_rel_step': 1e-6})
+ sm = min(res.fun, nearest_nyquist)
diff --git a/ROSCO_toolbox/linear/linear_models.py b/ROSCO_toolbox/linear/linear_models.py
index 6720ac57..9b46548a 100644
--- a/ROSCO_toolbox/linear/linear_models.py
+++ b/ROSCO_toolbox/linear/linear_models.py
@@ -17,7 +17,7 @@
try:
import pyFAST.linearization.mbc.mbc3 as mbc
except ImportError:
- import WEIS.control.mbc.mbc3 as mbc
+ import weis.control.mbc.mbc3 as mbc
except ImportError:
raise ImportError('Unable to load mbc3 from pyFAST or WEIS')
diff --git a/ROSCO_toolbox/linear/robust_scheduling.py b/ROSCO_toolbox/linear/robust_scheduling.py
index 169f53b9..4058ddcd 100644
--- a/ROSCO_toolbox/linear/robust_scheduling.py
+++ b/ROSCO_toolbox/linear/robust_scheduling.py
@@ -2,21 +2,19 @@
Methods for finding robust gain schedules
'''
-import scipy as sp
import numpy as np
import glob
import os
-import yaml
import openmdao.api as om
import matplotlib.pyplot as plt
import pandas as pd
-import copy
import multiprocessing as mp
from ROSCO_toolbox import controller as ROSCO_controller
from ROSCO_toolbox import turbine as ROSCO_turbine
from ROSCO_toolbox.linear.linear_models import LinearTurbineModel
from ROSCO_toolbox.linear.lin_util import add_pcomp, smargin
from ROSCO_toolbox.inputs.validation import load_rosco_yaml
+from ROSCO_toolbox.utilities import list_check
class RobustScheduling(om.ExplicitComponent):
'Finding Robust gain schedules for pitch controllers in FOWTs'
@@ -29,23 +27,76 @@ def setup(self):
# Options
linturb_options = self.options['linturb_options']
ROSCO_options = self.options['ROSCO_options']
-
- # Load ROSCO Controller
- self.controller, self.turbine = load_ROSCO(ROSCO_options['path_params'],
- ROSCO_options['turbine_params'],
- ROSCO_options['controller_params'])
+ ROSCO_options['controller_params']['omega_pc'] = list_check(
+ ROSCO_options['controller_params']['omega_pc'], return_bool=False)
+ ROSCO_options['controller_params']['zeta_pc'] = list_check(
+ ROSCO_options['controller_params']['zeta_pc'], return_bool=False)
+ if list_check(ROSCO_options['controller_params']['omega_pc']) or \
+ list_check(ROSCO_options['controller_params']['zeta_pc']):
+ raise AttributeError(
+ 'Error: omega_pc and zeta_pc must be scalars for robust controller tuning.')
+
+ # Load ROSCO Turbine and Controller
+ if 'dict_inputs' in ROSCO_options.keys(): # Allow for turbine parameters to be passed in as a dictionary
+ dict_inputs = ROSCO_options['dict_inputs']
+ # Define turbine based on inputs
+ self.turbine = type('', (), {})()
+ self.turbine.v_min = float(dict_inputs['v_min'])
+ self.turbine.J = float(dict_inputs['rotor_inertia'])
+ self.turbine.rho = float(dict_inputs['rho'])
+ self.turbine.rotor_radius = float(dict_inputs['R'])
+ self.turbine.Ng = float(dict_inputs['gear_ratio'])
+ # Incoming value already has gearbox eff included, so have to separate it out
+ self.turbine.GenEff = float(
+ dict_inputs['generator_efficiency']/dict_inputs['gearbox_efficiency']) * 100.
+ self.turbine.GBoxEff = float(dict_inputs['gearbox_efficiency']) * 100.
+ self.turbine.rated_rotor_speed = float(dict_inputs['rated_rotor_speed'])
+ self.turbine.rated_power = float(dict_inputs['rated_power'])
+ self.turbine.rated_torque = float(
+ dict_inputs['rated_torque']) / self.turbine.Ng * float(dict_inputs['gearbox_efficiency'])
+ self.turbine.v_rated = float(
+ dict_inputs['rated_rotor_speed'])*float(dict_inputs['R']) / float(dict_inputs['tsr_operational'])
+ self.turbine.v_min = float(dict_inputs['v_min'])
+ self.turbine.v_max = float(dict_inputs['v_max'])
+ self.turbine.max_pitch_rate = float(dict_inputs['max_pitch_rate'])
+ self.turbine.TSR_operational = float(dict_inputs['tsr_operational'])
+ self.turbine.max_torque_rate = float(dict_inputs['max_torque_rate'])
+ self.turbine.TowerHt = float(dict_inputs['TowerHt'])
+ self.turbine.Cp_table = np.squeeze(dict_inputs['Cp_table'])
+ self.turbine.Ct_table = np.squeeze(dict_inputs['Ct_table'])
+ self.turbine.Cq_table = np.squeeze(dict_inputs['Cq_table'])
+ self.turbine.pitch_initial_rad = dict_inputs['pitch_vector']
+ self.turbine.TSR_initial = dict_inputs['tsr_vector']
+ RotorPerformance = ROSCO_turbine.RotorPerformance
+ self.turbine.Cp = RotorPerformance(
+ self.turbine.Cp_table, self.turbine.pitch_initial_rad, self.turbine.TSR_initial)
+ self.turbine.Ct = RotorPerformance(
+ self.turbine.Ct_table, self.turbine.pitch_initial_rad, self.turbine.TSR_initial)
+ self.turbine.Cq = RotorPerformance(
+ self.turbine.Cq_table, self.turbine.pitch_initial_rad, self.turbine.TSR_initial)
+
+ self.controller = ROSCO_controller.Controller(ROSCO_options['controller_params'])
+ self.controller.tune_controller(self.turbine)
+ else:
+ self.controller, self.turbine = load_ROSCO(ROSCO_options['path_params'],
+ ROSCO_options['turbine_params'],
+ ROSCO_options['controller_params'])
# Load linear turbine models and trim them
- self.linturb = load_linturb(linturb_options['linfile_root'], load_parallel=linturb_options['load_parallel'])
+ self.linturb = load_linturb(
+ linturb_options['linfile_path'], load_parallel=linturb_options['load_parallel'])
self.linturb.trim_system(desInputs=['collective'], desOutputs=['RtSpeed'])
# Inputs
- self.add_input('u_eval', val=11., units='m/s', desc='Wind speeds to evaluate gain schedule')
+ self.add_input('u_eval', val=11., units='m/s',
+ desc='Wind speeds to evaluate gain schedule')
self.add_input('omega', val=0.1, units='rad/s', desc='Controller bandwidth')
- self.add_input('k_float', val=self.controller.Kp_float, units='s', desc='Floating feedback gain')
+ self.add_input('k_float', val=self.controller.Kp_float,
+ units='s', desc='Floating feedback gain')
# Outputs
self.add_output('sm', val=0.0, desc='Stability Margin')
- self.add_output('omega_opt', val=0.01, units='rad/s', desc='Maximized controller bandwidth')
+ self.add_output('omega_opt', val=0.01, units='rad/s',
+ desc='Maximized controller bandwidth')
def compute(self, inputs, outputs):
k_float = inputs['k_float'][0]
@@ -59,17 +110,18 @@ def compute(self, inputs, outputs):
sm = smargin(linturb, self.controller, inputs['u_eval'][0])
omega = inputs['omega']
-
+
# Outputs
- outputs['sm'] = sm
+ outputs['sm'] = sm
outputs['omega_opt'] = omega
+
class rsched_driver():
'''
A driver for scheduling robust controllers
'''
- def __init__(self,options):
+ def __init__(self, options):
self.linturb_options = options['linturb_options']
self.ROSCO_options = options['ROSCO_options']
self.path_options = options['path_options']
@@ -88,6 +140,11 @@ def __init__(self,options):
self.opt_options['levels'] = 20
elif self.opt_options['driver'] == 'optimization':
self.opt_options['levels'] = 10
+
+ # Clarify up input sizes
+ self.opt_options['omega'] = list_check(self.opt_options['omega'], return_bool=False)
+ self.opt_options['k_float'] = list_check(self.opt_options['k_float'], return_bool=False)
+
def setup(self):
'''
Setup the OpenMDAO problem
@@ -97,22 +154,25 @@ def setup(self):
# Add subsystem
self.om_problem.model.add_subsystem('r_sched', RobustScheduling(linturb_options=self.linturb_options,
- ROSCO_options=self.ROSCO_options))
+ ROSCO_options=self.ROSCO_options))
if self.opt_options['driver'] == 'design_of_experiments':
self.om_problem = self.init_doe(self.om_problem, levels=self.opt_options['levels'])
- if isinstance(self.opt_options['windspeed'], list):
+ if list_check(self.opt_options['windspeed']):
if len(self.opt_options['windspeed']) == 1:
self.opt_options['windspeed'] = self.opt_options['windspeed'][0]
else:
- ValueError('Can only run design of experiments for a single opt_options["windspeed"]')
+ ValueError(
+ 'Can only run design of experiments for a single opt_options["windspeed"]')
elif self.opt_options['driver'] == 'optimization':
self.om_problem = self.init_doe(self.om_problem, levels=self.opt_options['levels'])
else:
- ValueError("self.opt_options['driver'] must be either 'design_of_experiments' or 'optimization'.")
-
+ ValueError(
+ "self.opt_options['driver'] must be either 'design_of_experiments' or 'optimization'.")
+
# Add stability margin constraints
- self.om_problem.model.add_constraint('r_sched.sm', lower=self.opt_options['stability_margin'])
+ self.om_problem.model.add_constraint(
+ 'r_sched.sm', lower=self.opt_options['stability_margin'])
# Define objective
self.om_problem.model.add_objective('r_sched.omega_opt', scaler=-1)
@@ -121,11 +181,11 @@ def setup(self):
self.om_problem.setup()
# Set constant values
- if len(self.opt_options['omega']) == 1:
- self.om_problem.set_val('r_sched.omega', self.opt_options['omega'][0])
- if len(self.opt_options['k_float']) == 1:
- self.om_problem.set_val('r_sched.k_float', self.opt_options['k_float'][0])
-
+ if not list_check(self.opt_options['omega']):
+ self.om_problem.set_val('r_sched.omega', self.opt_options['omega'])
+ if not list_check(self.opt_options['k_float']):
+ self.om_problem.set_val('r_sched.k_float', self.opt_options['k_float'])
+
# Designate specific problem objects, add design variables
if self.opt_options['driver'] == 'design_of_experiments':
self.om_doe = self.om_problem
@@ -137,8 +197,7 @@ def setup(self):
self.om_opt = self.om_problem
self.om_opt = self.add_dv(self.om_opt, ['omega', 'k_float'])
self.om_opt = self.init_optimization(self.om_opt)
-
-
+
def execute(self):
'''
Execute OpenMDAO
@@ -156,37 +215,16 @@ def execute(self):
self.k_floats = []
self.sms = []
for u in self.opt_options['windspeed']:
- # Run initial doe
- # print('Finding initial condition for u = ', u)
- # self.om_doe.set_val('r_sched.u_eval', u)
- # self.doe_logfile = os.path.join(
- # self.output_dir, self.output_name + '.' + str(u) + ".doe.sql")
- # self.om_doe = self.setup_recorder(self.om_doe, self.doe_logfile)
- # self.om_doe.run_driver()
- # self.om_doe.cleanup()
-
- # # Load doe
- # doe_df = self.post_doe(save_csv=True)
- # doe_df.sort_values('r_sched.omega', inplace=True, ignore_index=True)
-
- # try:
- # # Find initial omega
- # om0 = np.mean(doe_df['r_sched.omega_opt'][doe_df['r_sched.sm'] > 0.0])
- # if np.isnan(om0):
- # raise
- # print('Found an initial condition:', om0)
-
- # except:
- # print('Unable to initialize om0 properly')
- # om0 = 0.05
- om0 = 0.01
+ om0 = 0.1
# Setup optimization
self.om_opt.set_val('r_sched.u_eval', u)
self.om_opt.set_val('r_sched.omega', om0)
# Run optimization
- print('Running optimization for u = ', u)
- opt_logfile = os.path.join(self.output_dir, self.output_name + '.' + str(u) + ".opt.sql")
+ print('Finding ROSCO tuning parameters for u = {}, sm = {}'.format(
+ u, self.linturb_options['stability_margin']))
+ opt_logfile = os.path.join(
+ self.output_dir, self.output_name + '.' + str(u) + ".opt.sql")
self.om_opt = self.setup_recorder(self.om_opt, opt_logfile)
self.om_opt.run_driver()
self.om_opt.cleanup()
@@ -210,15 +248,14 @@ def plot_schedule(self):
ax[2].grid()
plt.show()
-
def add_dv(self, om_problem, opt_vars):
'''add design variables'''
- if 'omega' in opt_vars and len(self.opt_options['omega']) == 2:
+ if 'omega' in opt_vars and list_check(self.opt_options['omega']):
om_problem.model.add_design_var(
- 'r_sched.omega', lower=self.opt_options['omega'][0], upper=self.opt_options['omega'][1])
+ 'r_sched.omega', lower=self.opt_options['omega'][0], upper=self.opt_options['omega'][1])
- if 'k_float' in opt_vars and len(self.opt_options['k_float']) == 2:
+ if 'k_float' in opt_vars and list_check(self.opt_options['k_float']):
om_problem.model.add_design_var(
'r_sched.k_float', lower=self.opt_options['k_float'][0], upper=self.opt_options['k_float'][1], ref=100)
@@ -233,7 +270,7 @@ def init_optimization(self, om_problem):
om_problem.driver.options['optimizer'] = 'SLSQP'
om_problem.driver.options['tol'] = 1e-3
om_problem.driver.options['maxiter'] = 20
- om_problem.driver.options['debug_print'] = ['desvars', 'nl_cons']
+ # om_problem.driver.options['debug_print'] = ['desvars', 'nl_cons']
om_problem.model.approx_totals(method="fd", step=1e-1, form='central', step_calc='rel')
return om_problem
@@ -245,25 +282,25 @@ def init_doe(self, om_problem, levels=20):
os.makedirs(self.output_dir, exist_ok=True)
# om_problem.driver.options['run_parallel'] = True
# om_problem.driver.options['procs_per_model'] = 1
-
+
return om_problem
@staticmethod
def setup_recorder(problem, sql_filename):
''' Used to prevent memory issues with OM sqlite recorder'''
recorder = om.SqliteRecorder(sql_filename)
-
- try: # Try to remove previous recorder
+
+ try: # Try to remove previous recorder
problem.driver._recorders.pop()
- except: # Must be first pass or optimization run
+ except: # Must be first pass or optimization run
pass
problem.driver.add_recorder(recorder)
-
- try: # try to re-run recorder setup
+
+ try: # try to re-run recorder setup
problem._setup_recording()
problem.driver._setup_recording()
- except: # Must be first pass
+ except: # Must be first pass
pass
return problem
@@ -275,7 +312,7 @@ def post_doe(self, save_csv=False):
doe_outfile = '.'.join(self.doe_logfile.split('.')[:-1]) + '.csv'
else:
doe_outfile = None
-
+
df = load_DOE(self.doe_logfile, outfile_name=doe_outfile)
return df
@@ -319,9 +356,10 @@ def load_DOE(doe_logs, outfile_name=None):
if outfile_name:
df.to_csv(outfile_name, index=False)
print('Saved {}'.format(outfile_name))
-
+
return df
+
def load_OMsql(log):
print('loading {}'.format(log))
cr = om.CaseReader(log)
@@ -336,18 +374,20 @@ def load_OMsql(log):
return rec_data
-def load_linturb(linfile_root, load_parallel=False):
+
+def load_linturb(linfile_path, load_parallel=False):
# Parse openfast linearization filenames
- filenames = glob.glob(os.path.join(linfile_root, '*.lin'))
+ filenames = glob.glob(os.path.join(linfile_path, '*.lin'))
linfiles = [os.path.split(file)[1] for file in filenames]
linroots = np.sort(np.unique([file.split('.')[0] for file in linfiles])).tolist()
linfile_numbers = set([int(file.split('.')[1]) for file in linfiles])
# Load linturb
- linturb = LinearTurbineModel(linfile_root, linroots,
+ linturb = LinearTurbineModel(linfile_path, linroots,
nlin=max(linfile_numbers), rm_hydro=True, load_parallel=load_parallel)
return linturb
+
def load_ROSCO(path_params, turbine_params, controller_params):
turbine = ROSCO_turbine.Turbine(turbine_params)
controller = ROSCO_controller.Controller(controller_params)
@@ -362,10 +402,10 @@ def load_ROSCO(path_params, turbine_params, controller_params):
if __name__ == '__main__':
# Setup linear turbine paths
- linfile_root = os.path.join(os.path.dirname(os.path.dirname(
+ linfile_path = os.path.join(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))), 'Test_Cases', 'IEA-15-240-RWT-UMaineSemi', 'linearizations')
load_parallel = True
- linturb_options = {'linfile_root': linfile_root,
+ linturb_options = {'linfile_path': linfile_path,
'load_parallel': load_parallel}
# ROSCO options
@@ -384,24 +424,24 @@ def load_ROSCO(path_params, turbine_params, controller_params):
}
# Path options
- output_dir = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'test_out' )
+ output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'test_out')
output_name = 'test'
path_options = {'output_dir': output_dir,
'output_name': output_name
}
# Scheduling options
- opt_options = { 'driver': 'optimization', #'design_of_experiments',
- 'windspeed': [12, 13, 14], #, 17, 18, 19, 20, 21, 22, 23, 24, 25],
+ opt_options = {'driver': 'optimization', # 'design_of_experiments',
+ 'windspeed': [12, 13, 14], # , 17, 18, 19, 20, 21, 22, 23, 24, 25],
'stability_margin': 0.1,
- 'omega':[0.05, 0.2],
+ 'omega': [0.05, 0.2],
'k_float': [0.0]}
-
+
options = {}
options['linturb_options'] = linturb_options
- options['ROSCO_options'] = ROSCO_options
- options['path_options'] = path_options
- options['opt_options'] = opt_options
+ options['ROSCO_options'] = ROSCO_options
+ options['path_options'] = path_options
+ options['opt_options'] = opt_options
sd = rsched_driver(options)
sd.setup()
@@ -411,3 +451,5 @@ def load_ROSCO(path_params, turbine_params, controller_params):
else:
sd.plot_schedule()
+if __name__ == '__main__':
+ pass
diff --git a/ROSCO_toolbox/ofTools/fast_io/output_processing.py b/ROSCO_toolbox/ofTools/fast_io/output_processing.py
index 25c051ec..970e9187 100644
--- a/ROSCO_toolbox/ofTools/fast_io/output_processing.py
+++ b/ROSCO_toolbox/ofTools/fast_io/output_processing.py
@@ -218,11 +218,11 @@ def plot_spectral(self, fastout=None, cases=None,
fig, ax - corresponds to generated figure
'''
- if not fastdict:
+ if not fastout:
try:
- fastdict = self.fastout
+ fastout = self.fastout
except:
- Error('Cannot plot OpenFAST output data before it is loaded with load_fast_out.')
+ raise AttributeError('Cannot plot OpenFAST output data before it is loaded with load_fast_out.')
if not cases:
cases=self.plot_cases
diff --git a/ROSCO_toolbox/utilities.py b/ROSCO_toolbox/utilities.py
index ada35de7..ebfa0988 100644
--- a/ROSCO_toolbox/utilities.py
+++ b/ROSCO_toolbox/utilities.py
@@ -20,14 +20,11 @@
write_rotor_performance
load_from_txt
DISCON_dict
+list_check
"""
import datetime
import os
import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib import transforms
-from itertools import takewhile, product
-import struct
import subprocess
import ROSCO_toolbox
@@ -67,7 +64,7 @@ def write_DISCON(turbine, controller, param_file='DISCON.IN', txt_filename='Cp_C
file.write('{0:<12d} ! F_LPFType - {{1: first-order low-pass filter, 2: second-order low-pass filter}}, [rad/s] (currently filters generator speed and pitch control signals\n'.format(int(controller.F_LPFType)))
file.write('{0:<12d} ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {{0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}}\n'.format(int(controller.F_NotchType)))
file.write('{0:<12d} ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {{0: off, 1: 1P reductions, 2: 1P+2P reductions}}\n'.format(int(controller.IPC_ControlMode)))
- file.write('{0:<12d} ! VS_ControlMode - Generator torque control mode in above rated conditions {{0: constant torque, 1: constant power, 2: TSR tracking PI control, 3: TSR tracking PI conrol with constant power}}\n'.format(int(controller.VS_ControlMode)))
+ file.write('{0:<12d} ! VS_ControlMode - Generator torque control mode in above rated conditions {{0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}}\n'.format(int(controller.VS_ControlMode)))
file.write('{0:<12d} ! PC_ControlMode - Blade pitch control mode {{0: No pitch, fix to fine pitch, 1: active PI blade pitch control}}\n'.format(int(controller.PC_ControlMode)))
file.write('{0:<12d} ! Y_ControlMode - Yaw control mode {{0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}}\n'.format(int(controller.Y_ControlMode)))
file.write('{0:<12d} ! SS_Mode - Setpoint Smoother mode {{0: no setpoint smoothing, 1: introduce setpoint smoothing}}\n'.format(int(controller.SS_Mode)))
@@ -495,3 +492,40 @@ def run_openfast(fast_dir, fastcall='openfast', fastfile=None, chdir=True):
# os.system('{} {}'.format(fastcall, os.path.join(fastfile)))
subprocess.run([fastcall, os.path.join(fastfile)], check=True, cwd=cwd)
print('OpenFAST simulation complete.')
+
+
+def list_check(x, return_bool=True):
+ '''
+ Check if the input is list-like or not
+
+ Parameters:
+ -----------
+ x: int, float, list, or np.ndarray
+ input to check
+ return_bool: bool
+ if true, returns True or False
+
+ '''
+ if isinstance(x, (int, float)):
+ y = x
+ is_list = False
+ elif isinstance(x, list):
+ if len(x) == 1:
+ y = x[0]
+ is_list = False
+ else:
+ y = x
+ is_list = True
+ elif isinstance(x, np.ndarray):
+ y = x
+ if x.size == 1:
+ is_list = False
+ else:
+ is_list = True
+ else:
+ raise AttributeError('Cannot run list_check for variable of type: {}'.format(type(x)))
+
+ if return_bool:
+ return is_list
+ else:
+ return y
diff --git a/Test_Cases/5MW_Land_Simulink/DISCON.IN b/Test_Cases/5MW_Land_Simulink/DISCON.IN
index 2571bbdb..7298a475 100644
--- a/Test_Cases/5MW_Land_Simulink/DISCON.IN
+++ b/Test_Cases/5MW_Land_Simulink/DISCON.IN
@@ -8,7 +8,7 @@
1 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals
0 ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
-2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}
1 ! PC_ControlMode - Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
0 ! Y_ControlMode - Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
1 ! SS_Mode - Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
diff --git a/Test_Cases/BAR_10/BAR_10_DISCON.IN b/Test_Cases/BAR_10/BAR_10_DISCON.IN
index 2c97ab52..f0cffc40 100644
--- a/Test_Cases/BAR_10/BAR_10_DISCON.IN
+++ b/Test_Cases/BAR_10/BAR_10_DISCON.IN
@@ -8,7 +8,7 @@
2 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals
0 ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
-2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}
1 ! PC_ControlMode - Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
0 ! Y_ControlMode - Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
1 ! SS_Mode - Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
diff --git a/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN b/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
index 78440efa..e967e2f4 100644
--- a/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
+++ b/Test_Cases/IEA-15-240-RWT-UMaineSemi/ServoData/DISCON-UMaineSemi.IN
@@ -8,7 +8,7 @@
2 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals
2 ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
-2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}
1 ! PC_ControlMode - Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
0 ! Y_ControlMode - Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
1 ! SS_Mode - Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
diff --git a/Test_Cases/NREL-5MW/DISCON.IN b/Test_Cases/NREL-5MW/DISCON.IN
index c1860ea7..23d1ea17 100644
--- a/Test_Cases/NREL-5MW/DISCON.IN
+++ b/Test_Cases/NREL-5MW/DISCON.IN
@@ -8,7 +8,7 @@
1 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals
0 ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
-3 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+3 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}
1 ! PC_ControlMode - Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
0 ! Y_ControlMode - Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
1 ! SS_Mode - Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
diff --git a/Tune_Cases/DISCON.IN b/Tune_Cases/DISCON.IN
index 7aaf62fc..a4df7c7c 100644
--- a/Tune_Cases/DISCON.IN
+++ b/Tune_Cases/DISCON.IN
@@ -8,7 +8,7 @@
2 ! F_LPFType - {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals
2 ! F_NotchType - Notch on the measured generator speed and/or tower fore-aft motion (for floating) {0: disable, 1: generator speed, 2: tower-top fore-aft motion, 3: generator speed and tower-top fore-aft motion}
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
-2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+2 ! VS_ControlMode - Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control with constant torque, 3: TSR tracking PI control with constant power}
1 ! PC_ControlMode - Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
0 ! Y_ControlMode - Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
1 ! SS_Mode - Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
diff --git a/Tune_Cases/IEA15MW_robust.yaml b/Tune_Cases/IEA15MW_robust.yaml
new file mode 100644
index 00000000..5be8820d
--- /dev/null
+++ b/Tune_Cases/IEA15MW_robust.yaml
@@ -0,0 +1,71 @@
+# --------------------- ROSCO controller tuning input file -------------------
+ # Written for use with ROSCO_Toolbox tuning procedures
+ # Turbine: IEA 15MW Reference Wind Turbine
+# ------------------------------ OpenFAST PATH DEFINITIONS ------------------------------
+path_params:
+ FAST_InputFile: 'IEA-15-240-RWT-UMaineSemi.fst' # Name of *.fst file
+ FAST_directory: '../Test_Cases/IEA-15-240-RWT-UMaineSemi' # Main OpenFAST model directory, where the *.fst lives
+ # Optional (but suggested...)
+ rotor_performance_filename: 'Cp_Ct_Cq.IEA15MW.txt' # Filename for rotor performance text file (if it has been generated by ccblade already)
+
+# -------------------------------- TURBINE PARAMETERS -----------------------------------
+turbine_params:
+ rotor_inertia: 310619488. # Rotor inertia [kg m^2], {Available in Elastodyn .sum file}
+ rated_rotor_speed: 0.7916813478 # Rated rotor speed [rad/s]
+ v_min: 3. # Cut-in wind speed [m/s]
+ v_rated: 10.74 # Rated wind speed [m/s]
+ v_max: 25.0 # Cut-out wind speed [m/s], -- Does not need to be exact (JUST ASSUME FOR NOW)
+ max_pitch_rate: 0.0349 # Maximum blade pitch rate [rad/s]
+ max_torque_rate: 4500000. # Maximum torque rate [Nm/s], {~1/4 VS_RtTq/s}
+ rated_power: 15000000. # Rated Power [W]
+ bld_edgewise_freq: 4.0324 # Blade edgewise first natural frequency [rad/s]
+ bld_flapwise_freq: 3.4872 # Blade flapwise first natural frequency [rad/s]
+ TSR_operational: 9.0
+
+#------------------------------- CONTROLLER PARAMETERS ----------------------------------
+controller_params:
+ # Controller flags
+ LoggingLevel: 1 # {0: write no debug files, 1: write standard output .dbg-file, 2: write standard output .dbg-file and complete avrSWAP-array .dbg2-file
+ F_LPFType: 2 # {1: first-order low-pass filter, 2: second-order low-pass filter}, [rad/s] (currently filters generator speed and pitch control signals)
+ F_NotchType: 0 # Notch on the measured generator speed {0: disable, 1: enable}
+ IPC_ControlMode: 0 # Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) {0: off, 1: 1P reductions, 2: 1P+2P reductions}
+ VS_ControlMode: 2 # Generator torque control mode in above rated conditions {0: constant torque, 1: constant power, 2: TSR tracking PI control}
+ PC_ControlMode: 1 # Blade pitch control mode {0: No pitch, fix to fine pitch, 1: active PI blade pitch control}
+ Y_ControlMode: 0 # Yaw control mode {0: no yaw control, 1: yaw rate control, 2: yaw-by-IPC}
+ SS_Mode: 1 # Setpoint Smoother mode {0: no setpoint smoothing, 1: introduce setpoint smoothing}
+ WE_Mode: 2 # Wind speed estimator mode {0: One-second low pass filtered hub height wind speed, 1: Immersion and Invariance Estimator (Ortega et al.)}
+ PS_Mode: 3 # Pitch saturation mode {0: no pitch saturation, 1: peak shaving, 2: Cp-maximizing pitch saturation, 3: peak shaving and Cp-maximizing pitch saturation}
+ SD_Mode: 0 # Shutdown mode {0: no shutdown procedure, 1: pitch to max pitch at shutdown}
+ Fl_Mode: 1 # Floating specific feedback mode {0: no nacelle velocity feedback, 1: nacelle velocity feedback}
+ Flp_Mode: 0 # Flap control mode {0: no flap control, 1: steady state flap angle, 2: Proportional flap control}
+ # Controller parameters
+ U_pc: [12,14, 15, 18, 20]
+ zeta_pc: [1.2] # Pitch controller desired damping ratio [-]
+ omega_pc: [0.15] # Pitch controller desired natural frequency [rad/s]
+ zeta_vs: 0.85 # Torque controller desired damping ratio [-]
+ omega_vs: 0.12 # Torque controller desired natural frequency [rad/s]
+ # Optional - these can be defined, but do not need to be
+ interp_type: sigma
+ # max_pitch: # None # Maximum pitch angle [rad], {default = 90 degrees}
+ min_pitch: 0.0 # Minimum pitch angle [rad], {default = 0 degrees}
+ vs_minspd: 0.523598775 # Minimum rotor speed [rad/s], {default = 0 rad/s}
+ # ss_cornerfreq: # None # First order low-pass filter cornering frequency for setpoint smoother [rad/s]
+ # ss_vsgain: # None # Torque controller setpoint smoother gain bias percentage [%, <= 1 ], {default = 100%}
+ # ss_pcgain: # None # Pitch controller setpoint smoother gain bias percentage [%, <= 1 ], {default = 0.1%}
+ ps_percent: 0.8 # None # Percent peak shaving [%, <= 1 ], {default = 80%}
+ # sd_maxpit: # None # Maximum blade pitch angle to initiate shutdown [rad], {default = bld pitch at v_max}
+ # sd_cornerfreq: # None # Cutoff Frequency for first order low-pass filter for blade pitch angle [rad/s], {default = 0.41888 ~ time constant of 15s}
+ # flp_maxpit: # None # Maximum (and minimum) flap pitch angle [rad]
+ twr_freq: 3.355 # for semi only!
+ ptfm_freq: 0.213 # for semi only!
+
+linmodel_tuning:
+ type: 'robust' # Type of level 2 based tuning - robust gain scheduling (robust) or simulation based optimization (simulation)
+ linfile_path: '../Test_Cases/IEA-15-240-RWT-UMaineSemi/linearizations' # Path to OpenFAST linearization (.lin) files, if they exist
+ linfile_root: 'lin' # Root name of OpenFAST linearization (.lin) files, if they exist
+ lintune_outpath: 'lintune_outfiles' # Path for outputs from linear model-based analysis
+ load_parallel: True # Load linearization files in parallel (True/False)
+ stability_margin: 0.1 # Desired maximum stability margin
+ omega_pc: # constraints on maximum and minimum allowable pitch controller bandwidth
+ max: 0.3
+ min: 0.0
diff --git a/docs/index.rst b/docs/index.rst
index a6d0e335..9595488a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -5,9 +5,9 @@ ROSCO Documentation
:Version: |release|
:Date: |today|
-NREL's Reference OpenSource Controller (ROSCO) toolbox for wind turbine applications is a toolbox designed to ease controller implementation for the wind turbine researcher. The purpose of these documents is to provide information for the use of the ROSCO related toolchain.
+NREL's Reference OpenSource Controller (ROSCO) tool-set for wind turbine applications designed to ease controller implementation for the wind turbine researcher. The purpose of these documents is to provide information for the use of the tool-set.
-Figure :numref:`fig-RT` shows the general workflow for the ROSCO toolchain.
+Figure :numref:`fig-RT` shows the general workflow for the ROSCO tool-chain.
.. _fig-RT:
.. figure:: /source/figures/ROSCO_toolbox.png
diff --git a/docs/source/install.rst b/docs/source/install.rst
index 0d3936b9..cd4b2e23 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -6,7 +6,7 @@ Installing the ROSCO tools
===========================
Depending on what is needed, a user can choose to use just the ROSCO controller or to use both the ROSCO controller and the toolbox. Both the controller and the toolbox should be installed if one wishes to leverage the full ROSCO toolchain.
-It is recommended to install the ROSCO toolset in full following the instruction in :ref:`full_rosco`
+It is recommended to install the ROSCO tool-set in full following the instruction in :ref:`full_rosco`
For users planning to only download and/or compile the ROSCO controller, please follow the instructions on :ref:`rosco_controller`.
@@ -15,11 +15,11 @@ For users planning to only download and/or compile the ROSCO controller, please
Full ROSCO
----------
-We recommend using the full ROSCO toolchain. This also eases the installation process.
+We recommend using the full ROSCO tool-chain. This also eases the installation process.
Installing
..............
-Installation of the complete ROSCO toolset is made easy through `Anaconda `_. If you do not already have Anaconda installed on your machine, please install it. Additionally, we primarily support the use of CMake_ to control the software compilation process. If you plan to compile the ROSCO controller's source code, we request that you download CMake as well.
+Installation of the complete ROSCO tool-set is made easy through `Anaconda `_. If you do not already have Anaconda installed on your machine, please install it. Additionally, we primarily support the use of CMake_ to control the software compilation process. If you plan to compile the ROSCO controller's source code, we request that you download CMake as well. You are free to compile ROSCO without the use of CMake, but we cannot guarantee support for all of the *many* available methods of compiling.
Then please follow the following steps:
@@ -37,7 +37,7 @@ Then please follow the following steps:
conda install -y wisdem
-You should then do step 3a *or* 3b.
+You should then do step 3 *or* 4.
If you do not want to compile the ROSCO controller while also installing the ROSCO toolbox, please follow the instructions for :ref:`compiling_rosco`.
3. Clone and Install the ROSCO toolbox with ROSCO
@@ -61,7 +61,7 @@ If you do not want to compile the ROSCO controller while also installing the ROS
**Alternatively...**
-If you wish to write your own scripts to leverage the ROSCO tools, but do not necessarily need the source code or to run any of the examples, the ROSCO toolbox is available via Conda-Forge:
+If you wish to write your own scripts to leverage the ROSCO tools, but do not necessarily need the source code or to run any of the examples, ROSCO is available via Conda-Forge:
::
@@ -89,7 +89,7 @@ We primarily support the use of CMake_ for setting up the necessary build files
If one wishes to download the code via the command line, we provide two supported options in the subsections below.
For non-developers (those not interested in modifying the source code), the a 64-bit version of the compiled controller can be downloaded via Anaconda.
-For users needing a 32-bit version on Windows and/or developers, CMake can be used to properly compile the Fortran code.
+For developerrs and users needing a 32-bit version on Windows, CMake can be used to properly compile the Fortran code.
.. _compiling_rosco:
@@ -97,7 +97,7 @@ For users needing a 32-bit version on Windows and/or developers, CMake can be us
Anaconda download for non-developers
.....................................
-For users familiar with Anaconda_, a 64-bit version of ROSCO is available through the conda-forge channel.
+For users familiar with Anaconda_, the tagged 64-bit versions of ROSCO are available through the conda-forge channel.
In order to download the most recently compiled version release, from an anaconda powershell (Windows) or terminal (Mac/Linux) window, create a new anaconda virtual environment:
::
@@ -132,7 +132,7 @@ on Windows.
CMake for developers (Mac/linux)
.................................
-CMake_ provides a straightforward option for many users, particularly those on a Mac or Linux. We recommend that users use CMake if at all possible, as it is more difficult for us to support the use of other tools to aid with compiling ROSCO
+CMake_ provides a straightforward option for many users, particularly those on a Mac or Linux. We recommend that users use CMake if at all possible, as it is more difficult for us to support the use of other tools to aid with compiling ROSCO.
On Mac/Linux, ROSCO can be compiled by first cloning the source code from git using:
::
diff --git a/docs/source/standard_use.rst b/docs/source/standard_use.rst
index 79575e62..48158944 100644
--- a/docs/source/standard_use.rst
+++ b/docs/source/standard_use.rst
@@ -57,6 +57,10 @@ ROSCO also contains a method for distributed aerodynamic control (e.g., via trai
* :code:`example_10.py` tunes a controller for distributed aerodynamic control
+The ROSCO toolbox also contains methods for working with OpenFAST linear models
+* :code:`example_11.py` exports a file of the parameters used for the simplified linear models used to tune ROSCO
+* :code:`example_12.py` shows how linear models generated using OpenFAST can be used to tune controllers with robust stability properties.
+
Running OpenFAST Simulations
----------------------------
diff --git a/setup.py b/setup.py
index f7e9499a..ff0dd918 100644
--- a/setup.py
+++ b/setup.py
@@ -41,7 +41,7 @@
EMAIL = 'nikhar.abbas@nrel.gov'
AUTHOR = 'NREL, National Wind Technology Center'
REQUIRES_PYTHON = '>=3.4'
-VERSION = '2.3.0'
+VERSION = '2.4.0'
# These packages are required for all of the code to be executed.
# - Maybe you can get away with older versions...