From df133ee6317f99eae9490509434cc9e1b8d80a73 Mon Sep 17 00:00:00 2001 From: AbhineetGupta Date: Fri, 9 Aug 2024 19:10:55 -0600 Subject: [PATCH] Added autodocumentation template for all examples --- Examples/02_ccblade.py | 85 ++++---- Examples/03_tune_controller.py | 118 ++++++----- Examples/04_simple_sim.py | 159 +++++++------- Examples/05_openfast_sim.py | 96 ++++----- Examples/06_peak_shaving.py | 105 +++++----- Examples/07_openfast_outputs.py | 70 ++++--- Examples/08_run_turbsim.py | 30 +-- Examples/09_distributed_aero.py | 79 +++---- Examples/10_linear_params.py | 115 ++++++----- Examples/11_robust_tuning.py | 24 ++- Examples/12_tune_ipc.py | 16 +- Examples/14_open_loop_control.py | 287 +++++++++++++------------- Examples/15_pass_through.py | 27 ++- Examples/16_external_dll.py | 22 +- Examples/17a_zeromq_simple.py | 83 ++++---- Examples/17b_zeromq_multi_openfast.py | 138 +++++++------ Examples/18_pitch_offsets.py | 22 +- Examples/19_update_discon_version.py | 15 +- Examples/20_active_wake_control.py | 269 ++++++++++++------------ Examples/21_optional_inputs.py | 22 +- Examples/22_cable_control.py | 40 ++-- Examples/23_structural_control.py | 44 ++-- Examples/24_floating_feedback.py | 30 ++- Examples/25_rotor_position_control.py | 22 +- Examples/26_marine_hydro.py | 30 +-- Examples/27_power_ref_control.py | 34 ++- Examples/28_tower_resonance.py | 27 +-- docs/source/examples.rst | 27 +++ 28 files changed, 1022 insertions(+), 1014 deletions(-) diff --git a/Examples/02_ccblade.py b/Examples/02_ccblade.py index 581603d7..366d973d 100644 --- a/Examples/02_ccblade.py +++ b/Examples/02_ccblade.py @@ -1,46 +1,53 @@ -''' ------------ 02_ccblade -------------- -Run CCblade, save a rotor performance text file -------------------------------------- - +""" +02_ccblade +-------------- +Run CCblade, save a rotor performance text file. In this example: -- Read .yaml input file -- Load an openfast turbine model -- Run ccblade to get rotor performance properties -- Write a text file with rotor performance properties -''' + +* Read .yaml input file +* Load an openfast turbine model +* Run ccblade to get rotor performance properties +* Write a text file with rotor performance properties +""" + # Python modules import os # ROSCO toolbox modules from rosco.toolbox import turbine as ROSCO_turbine from rosco.toolbox.utilities import write_rotor_performance from rosco.toolbox.inputs.validation import load_rosco_yaml -# Initialize parameter dictionaries -turbine_params = {} -control_params = {} - -this_dir = os.path.dirname(os.path.abspath(__file__)) -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) - -# Load yaml file -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') -parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Load turbine data from openfast model -turbine = ROSCO_turbine.Turbine(turbine_params) -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='cc-blade', - txt_filename=None) - -# Write rotor performance text file -txt_filename = os.path.join(example_out_dir,'02_Cp_Ct_Cq.Ex03.txt') -write_rotor_performance(turbine,txt_filename=txt_filename) + +def main(): + # Initialize parameter dictionaries + turbine_params = {} + control_params = {} + + this_dir = os.path.dirname(os.path.abspath(__file__)) + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) + + # Load yaml file + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Load turbine data from openfast model + turbine = ROSCO_turbine.Turbine(turbine_params) + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='cc-blade', + txt_filename=None) + + # Write rotor performance text file + txt_filename = os.path.join(example_out_dir,'02_Cp_Ct_Cq.Ex03.txt') + write_rotor_performance(turbine,txt_filename=txt_filename) + +if __name__ == "__main__": + main() + diff --git a/Examples/03_tune_controller.py b/Examples/03_tune_controller.py index 9d426e50..e4f0e1b8 100644 --- a/Examples/03_tune_controller.py +++ b/Examples/03_tune_controller.py @@ -1,15 +1,16 @@ -''' ------------ 03_tune_controller -------------- +""" +03_tune_controller +------------------ Load a turbine model and tune the controller -------------------------------------- - In this example: - - Read a .yaml file - - Load a turbine model from OpenFAST - - Tune a controller - - Write a controller input file - - Plot gain schedule -''' + +* Read a .yaml file +* Load a turbine model from OpenFAST +* Tune a controller +* Write a controller input file +* Plot gain schedule +""" + # Python modules import matplotlib.pyplot as plt import os @@ -19,62 +20,65 @@ from rosco.toolbox.utilities import write_DISCON from rosco.toolbox.inputs.validation import load_rosco_yaml +def main(): + # Load yaml file + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] -# Load yaml file -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') -parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) -# Instantiate turbine, controller, and file processing classes -turbine = ROSCO_turbine.Turbine(turbine_params) -controller = ROSCO_controller.Controller(controller_params) + # Load turbine data from OpenFAST and rotor performance text file + cp_filename = os.path.join(tune_dir,path_params['rotor_performance_filename']) + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='txt',txt_filename= cp_filename + ) -# Load turbine data from OpenFAST and rotor performance text file -cp_filename = os.path.join(tune_dir,path_params['rotor_performance_filename']) -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='txt',txt_filename= cp_filename - ) + # Tune controller + controller.tune_controller(turbine) -# Tune controller -controller.tune_controller(turbine) + # Write parameter input file + param_file = os.path.join(this_dir,'DISCON.IN') + write_DISCON(turbine,controller, + param_file=param_file, + txt_filename=cp_filename + ) -# Write parameter input file -param_file = os.path.join(this_dir,'DISCON.IN') -write_DISCON(turbine,controller, -param_file=param_file, -txt_filename=cp_filename -) + # Plot gain schedule + fig, ax = plt.subplots(2,2,constrained_layout=True,sharex=True) + ax = ax.flatten() + ax[0].plot(controller.v[len(controller.v_below_rated)+1:], controller.omega_pc_U) + ax[0].set_ylabel('omega_pc') -# Plot gain schedule -fig, ax = plt.subplots(2,2,constrained_layout=True,sharex=True) -ax = ax.flatten() -ax[0].plot(controller.v[len(controller.v_below_rated)+1:], controller.omega_pc_U) -ax[0].set_ylabel('omega_pc') + ax[1].plot(controller.v[len(controller.v_below_rated)+1:], controller.zeta_pc_U) + ax[1].set_ylabel('zeta_pc') -ax[1].plot(controller.v[len(controller.v_below_rated)+1:], controller.zeta_pc_U) -ax[1].set_ylabel('zeta_pc') + ax[2].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Kp) + ax[2].set_xlabel('Wind Speed') + ax[2].set_ylabel('Proportional Gain') -ax[2].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Kp) -ax[2].set_xlabel('Wind Speed') -ax[2].set_ylabel('Proportional Gain') + ax[3].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Ki) + ax[3].set_xlabel('Wind Speed') + ax[3].set_ylabel('Integral Gain') -ax[3].plot(controller.v[len(controller.v_below_rated)+1:], controller.pc_gain_schedule.Ki) -ax[3].set_xlabel('Wind Speed') -ax[3].set_ylabel('Integral Gain') + plt.suptitle('Pitch Controller Gains') -plt.suptitle('Pitch Controller Gains') + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir,'03_GainSched.png')) -if False: - plt.show() -else: - plt.savefig(os.path.join(example_out_dir,'03_GainSched.png')) \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Examples/04_simple_sim.py b/Examples/04_simple_sim.py index a19a2ccb..acd0848e 100644 --- a/Examples/04_simple_sim.py +++ b/Examples/04_simple_sim.py @@ -1,19 +1,18 @@ -''' ------------ 04_simple_sim -------------- +""" +04_simple_sim +------------- Run and plot a simple simple step wind simulation -------------------------------------- - In this example: - - Load turbine from saved pickle - - Tune a controller - - Run and plot a simple step wind simulation - -Notes - You will need to have a compiled controller in ROSCO, and - properly point to it in the `lib_name` variable. - - Using wind speed estimators in this simple simulation is - known to cause problems. We suggesting using WE_Mode = 0 in DISCON.IN - or increasing sampling rate of simulation -''' + +* Load turbine from saved pickle +* Tune a controller +* Run and plot a simple step wind simulation + +Notes: + +* You will need to have a compiled controller in ROSCO, and properly point to it in the `lib_name` variable. +* Using wind speed estimators in this simple simulation is known to cause problems. We suggesting using WE_Mode = 0 in DISCON.IN or increasing sampling rate of simulation +""" # Python modules import matplotlib.pyplot as plt import numpy as np @@ -27,80 +26,80 @@ from rosco.toolbox.utilities import write_DISCON from rosco.toolbox.inputs.validation import load_rosco_yaml - -# Load yaml file -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') -parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Specify controller dynamic library path and name - -#directories -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - -# # Load turbine model from saved pickle -turbine = ROSCO_turbine.Turbine -turbine = turbine.load(os.path.join(example_out_dir,'01_NREL5MW_saved.p')) -# controller = ROSCO_controller.Controller(controller_params) - -# Load turbine data from OpenFAST and rotor performance text file -cp_filename = os.path.join(tune_dir,path_params['rotor_performance_filename']) -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='txt',txt_filename=cp_filename +def main(): + # Load yaml file + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Specify controller dynamic library path and name + + #directories + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) + + # # Load turbine model from saved pickle + turbine = ROSCO_turbine.Turbine + turbine = turbine.load(os.path.join(example_out_dir,'01_NREL5MW_saved.p')) + # controller = ROSCO_controller.Controller(controller_params) + + # Load turbine data from OpenFAST and rotor performance text file + cp_filename = os.path.join(tune_dir,path_params['rotor_performance_filename']) + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='txt',txt_filename=cp_filename + ) + + # Tune controller + controller = ROSCO_controller.Controller(controller_params) + controller.tune_controller(turbine) + + # Write parameter input file + param_filename = os.path.join(this_dir,'DISCON.IN') + write_DISCON( + turbine,controller, + param_file=param_filename, + txt_filename=cp_filename ) -# Tune controller -controller = ROSCO_controller.Controller(controller_params) -controller.tune_controller(turbine) - -# Write parameter input file -param_filename = os.path.join(this_dir,'DISCON.IN') -write_DISCON( - turbine,controller, - param_file=param_filename, - txt_filename=cp_filename - ) - -# Load controller library -controller_int = ROSCO_ci.ControllerInterface(lib_name,param_filename=param_filename,sim_name='sim1') + # Load controller library + controller_int = ROSCO_ci.ControllerInterface(lib_name,param_filename=param_filename,sim_name='sim1') -# Load the simulator -sim_1 = ROSCO_sim.Sim(turbine,controller_int) + # Load the simulator + sim_1 = ROSCO_sim.Sim(turbine,controller_int) -# Define a wind speed history -dt = 0.025 -tlen = 1000 # length of time to simulate (s) -ws0 = 7 # initial wind speed (m/s) -t= np.arange(0,tlen,dt) -ws = np.ones_like(t) * ws0 -# add steps at every 100s -for i in range(len(t)): - ws[i] = ws[i] + t[i]//100 + # Define a wind speed history + dt = 0.025 + tlen = 1000 # length of time to simulate (s) + ws0 = 7 # initial wind speed (m/s) + t= np.arange(0,tlen,dt) + ws = np.ones_like(t) * ws0 + # add steps at every 100s + for i in range(len(t)): + ws[i] = ws[i] + t[i]//100 -# Run simulator and plot results -sim_1.sim_ws_series(t,ws,rotor_rpm_init=4) + # Run simulator and plot results + sim_1.sim_ws_series(t,ws,rotor_rpm_init=4) -# Load controller library again to see if we deallocated properly -controller_int = ROSCO_ci.ControllerInterface(lib_name,param_filename=param_filename,sim_name='sim_2') + # Load controller library again to see if we deallocated properly + controller_int = ROSCO_ci.ControllerInterface(lib_name,param_filename=param_filename,sim_name='sim_2') -# Run simulator again and plot results -sim_2 = ROSCO_sim.Sim(turbine,controller_int) -sim_2.sim_ws_series(t,ws,rotor_rpm_init=4) + # Run simulator again and plot results + sim_2 = ROSCO_sim.Sim(turbine,controller_int) + sim_2.sim_ws_series(t,ws,rotor_rpm_init=4) -# Check if simulations are equal -np.testing.assert_almost_equal(sim_1.gen_speed,sim_2.gen_speed) + # Check if simulations are equal + np.testing.assert_almost_equal(sim_1.gen_speed,sim_2.gen_speed) -if False: - plt.show() -else: - plt.savefig(os.path.join(example_out_dir,'04_NREL5MW_SimpSim.png')) + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir,'04_NREL5MW_SimpSim.png')) diff --git a/Examples/05_openfast_sim.py b/Examples/05_openfast_sim.py index 3a45d40d..c552f6d7 100644 --- a/Examples/05_openfast_sim.py +++ b/Examples/05_openfast_sim.py @@ -1,15 +1,18 @@ -''' ------------ 05_openfast_sim -------------- +""" +05_openfast_sim +--------------- Load a turbine, tune a controller, run OpenFAST simulation -------------------------------------- - In this example: - - Load a turbine from OpenFAST - - Tune a controller - - Run an OpenFAST simulation -Note - you will need to have a compiled controller in ROSCO/build/ -''' +* Load a turbine from OpenFAST +* Tune a controller +* Run an OpenFAST simulation + +Note + +* you will need to have a compiled controller in ROSCO/build/ +""" + # Python Modules #import yaml import os @@ -21,48 +24,47 @@ from rosco.toolbox.utilities import write_DISCON, run_openfast from rosco.toolbox.inputs.validation import load_rosco_yaml +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + example_out_dir = os.path.join(this_dir,'examples_out') -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') -example_out_dir = os.path.join(this_dir,'examples_out') - -# Load yaml file -parameter_filename = os.path.join(tune_dir, 'IEA15MW_MultiOmega.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Instantiate turbine, controller, and file processing classes -turbine = ROSCO_turbine.Turbine(turbine_params) -controller = ROSCO_controller.Controller(controller_params) + # Load yaml file + parameter_filename = os.path.join(tune_dir, 'IEA15MW_MultiOmega.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] -# Load turbine data from OpenFAST and rotor performance text file -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='txt', - txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) - ) - -# Tune controller -controller.tune_controller(turbine) + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) -# Write parameter input file -param_file = os.path.join(this_dir,'DISCON.IN') # This must be named DISCON.IN to be seen by the compiled controller binary. -write_DISCON(turbine,controller,param_file=param_file, txt_filename=path_params['rotor_performance_filename']) - -# Run OpenFAST -# --- May need to change fastcall if you use a non-standard, conda-installed command to call openfast -# If you run the `fastcall` from the command line where you run this script, it should run OpenFAST -fastcall = 'openfast' -run_openfast( - os.path.join(tune_dir,path_params['FAST_directory']), - fastcall=fastcall, - fastfile=path_params['FAST_InputFile'], - chdir=True - ) + # Load turbine data from OpenFAST and rotor performance text file + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='txt', + txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + ) + # Tune controller + controller.tune_controller(turbine) + # Write parameter input file + param_file = os.path.join(this_dir,'DISCON.IN') # This must be named DISCON.IN to be seen by the compiled controller binary. + write_DISCON(turbine,controller,param_file=param_file, txt_filename=path_params['rotor_performance_filename']) + # Run OpenFAST + # --- May need to change fastcall if you use a non-standard, conda-installed command to call openfast + # If you run the `fastcall` from the command line where you run this script, it should run OpenFAST + fastcall = 'openfast' + run_openfast( + os.path.join(tune_dir,path_params['FAST_directory']), + fastcall=fastcall, + fastfile=path_params['FAST_InputFile'], + chdir=True + ) +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Examples/06_peak_shaving.py b/Examples/06_peak_shaving.py index 9bfa5228..2edfb3e5 100644 --- a/Examples/06_peak_shaving.py +++ b/Examples/06_peak_shaving.py @@ -1,14 +1,14 @@ -''' ------------ 06_peak_shavings -------------- +""" +06_peak_shavings +---------------- Load saved turbine, tune controller, plot minimum pitch schedule -------------------------------------- - In this example: - - Load a yaml file - - Load a turbine from openfast - - Tune a controller - - Plot minimum pitch schedule -''' + +* Load a yaml file +* Load a turbine from openfast +* Tune a controller +* Plot minimum pitch schedule +""" # Python modules import matplotlib.pyplot as plt @@ -18,46 +18,49 @@ from rosco.toolbox import turbine as ROSCO_turbine from rosco.toolbox.inputs.validation import load_rosco_yaml +def main(): + this_dir = os.path.dirname(__file__) + tune_dir = os.path.join(this_dir,'Tune_Cases') + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) + + # Load yaml file + parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Ensure minimum generator speed at 50 rpm (for example's sake), turn on peak shaving and cp-maximizing min pitch + controller_params['vs_minspd'] = 50/97 + controller_params['PS_Mode'] = 3 + + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) + + # Load turbine data from OpenFAST and rotor performance text file + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + ) + # Tune controller + controller.tune_controller(turbine) + + # Plot minimum pitch schedule + fig, ax = plt.subplots(1,1) + ax.plot(controller.v, controller.pitch_op,label='Steady State Operation') + ax.plot(controller.v, controller.ps_min_bld_pitch, label='Minimum Pitch Schedule') + ax.legend() + ax.set_xlabel('Wind speed (m/s)') + ax.set_ylabel('Blade pitch (rad)') + + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir,'06_MinPitch.png')) -this_dir = os.path.dirname(__file__) -tune_dir = os.path.join(this_dir,'Tune_Cases') -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) - -# Load yaml file -parameter_filename = os.path.join(tune_dir,'NREL5MW.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Ensure minimum generator speed at 50 rpm (for example's sake), turn on peak shaving and cp-maximizing min pitch -controller_params['vs_minspd'] = 50/97 -controller_params['PS_Mode'] = 3 - -# Instantiate turbine, controller, and file processing classes -turbine = ROSCO_turbine.Turbine(turbine_params) -controller = ROSCO_controller.Controller(controller_params) - -# Load turbine data from OpenFAST and rotor performance text file -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='txt',txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) - ) -# Tune controller -controller.tune_controller(turbine) - -# Plot minimum pitch schedule -fig, ax = plt.subplots(1,1) -ax.plot(controller.v, controller.pitch_op,label='Steady State Operation') -ax.plot(controller.v, controller.ps_min_bld_pitch, label='Minimum Pitch Schedule') -ax.legend() -ax.set_xlabel('Wind speed (m/s)') -ax.set_ylabel('Blade pitch (rad)') - -if False: - plt.show() -else: - plt.savefig(os.path.join(example_out_dir,'06_MinPitch.png')) +if __name__ == "__main__": + main() diff --git a/Examples/07_openfast_outputs.py b/Examples/07_openfast_outputs.py index ed19bc63..f92f7125 100644 --- a/Examples/07_openfast_outputs.py +++ b/Examples/07_openfast_outputs.py @@ -1,15 +1,15 @@ -''' ------------ 07_openfast_outputss -------------- +""" +07_openfast_outputs +------------------- Plot some OpenFAST output data -------------------------------------- - In this example: - - Load openfast output data - - Trim the time series - - Plot some available channels + +* Load openfast output data +* Trim the time series +* Plot some available channels Note: need to run openfast model in 'Test_Cases/5MW_Land_DLL_WTurb/' to plot -''' +""" # Python Modules #import numpy as np @@ -18,37 +18,39 @@ from rosco.toolbox.ofTools.fast_io import output_processing import os -this_dir = os.path.dirname(os.path.abspath(__file__)) -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) - -# Define openfast output filenames -filenames = ["Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi.outb"] -# ---- Note: Could load and plot multiple cases, textfiles, and binaries... -# filenames = ["../Test_Cases/NREL-5MW/NREL-5MW.outb", -# "../Test_Cases/NREL-5MW/NREL-5MW_ex8.outb"] +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) -filenames = [os.path.join(this_dir,file) for file in filenames] + # Define openfast output filenames + filenames = ["Test_Cases/IEA-15-240-RWT-UMaineSemi/IEA-15-240-RWT-UMaineSemi.outb"] + # ---- Note: Could load and plot multiple cases, textfiles, and binaries... + # filenames = ["../Test_Cases/NREL-5MW/NREL-5MW.outb", + # "../Test_Cases/NREL-5MW/NREL-5MW_ex8.outb"] -# Define Plot cases -# --- Comment,uncomment, create, and change these as desired... -cases = {} -cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed'] -# cases['Rotor'] = ['BldPitch1', 'GenTq', 'GenPwr'] -# cases['Platform Motion'] = ['PtfmSurge', 'PtfmSway', 'PtfmHeave', 'PtfmPitch','PtfmRoll','PtfmYaw'] + filenames = [os.path.join(this_dir,file) for file in filenames] + # Define Plot cases + # --- Comment,uncomment, create, and change these as desired... + cases = {} + cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed'] + # cases['Rotor'] = ['BldPitch1', 'GenTq', 'GenPwr'] + # cases['Platform Motion'] = ['PtfmSurge', 'PtfmSway', 'PtfmHeave', 'PtfmPitch','PtfmRoll','PtfmYaw'] -# Instantiate fast_IO -fast_out = output_processing.output_processing() -# Can also do: -# fast_out = output_processing.output_processing(filenames=filenames, cases=cases) -# fast_out.plot_fast_out() -# Load and plot -fastout = fast_out.load_fast_out(filenames) -fast_out.plot_fast_out(cases=cases,showplot=False) + # Instantiate fast_IO + fast_out = output_processing.output_processing() + # Can also do: + # fast_out = output_processing.output_processing(filenames=filenames, cases=cases) + # fast_out.plot_fast_out() -plt.savefig(os.path.join(example_out_dir,'07_IEA-15MW_Semi_Out.png')) + # Load and plot + fastout = fast_out.load_fast_out(filenames) + fast_out.plot_fast_out(cases=cases,showplot=False) + plt.savefig(os.path.join(example_out_dir,'07_IEA-15MW_Semi_Out.png')) +if __name__ == "__main__": + main() diff --git a/Examples/08_run_turbsim.py b/Examples/08_run_turbsim.py index bbacb93e..321b3776 100644 --- a/Examples/08_run_turbsim.py +++ b/Examples/08_run_turbsim.py @@ -1,23 +1,27 @@ -''' ------------ 08_run_turbsim -------------- +""" +08_run_turbsim +-------------- Run TurbSim to create wind field binary -------------------------------------- - In this example: - - Leverage the run_openfast functionality to compile a turbsim binary -''' + +* Leverage the run_openfast functionality to compile a turbsim binary +""" # ROSCO toolbox modules from rosco.toolbox.utilities import run_openfast import os -this_dir = os.path.dirname(os.path.abspath(__file__)) +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + + # Define openfast output filenames + wind_directory = os.path.join(this_dir,'Test_Cases/Wind/') + turbsim_infile = '90m_12mps_twr.inp' -# Define openfast output filenames -wind_directory = os.path.join(this_dir,'Test_Cases/Wind/') -turbsim_infile = '90m_12mps_twr.inp' + run_openfast(wind_directory, fastcall='turbsim', + fastfile=os.path.join(wind_directory, turbsim_infile), chdir=False) -run_openfast(wind_directory, fastcall='turbsim', - fastfile=os.path.join(wind_directory, turbsim_infile), chdir=False) + print('test') -print('test') +if __name__ == "__main__": + main() diff --git a/Examples/09_distributed_aero.py b/Examples/09_distributed_aero.py index 810de3f2..322e909b 100644 --- a/Examples/09_distributed_aero.py +++ b/Examples/09_distributed_aero.py @@ -1,19 +1,19 @@ -''' ------------ 09_distributed_aero -------------- +""" +09_distributed_aero +------------------- Tune a controller for distributed aerodynamic control -------------------------------------- - In this example: -- Read .yaml input file -- Load an openfast turbine model -- Read text file with rotor performance properties -- Load blade information -- Tune controller with flap actuator -Note: You will need a turbine model with DAC capabilites in order to run this. - The curious user can contact Nikhar Abbas (nikhar.abbas@nrel.gov) for available - models, if they do not have any themselves. -''' +* Read .yaml input file +* Load an openfast turbine model +* Read text file with rotor performance properties +* Load blade information +* Tune controller with flap actuator + +Note + +* You will need a turbine model with DAC capabilites in order to run this. The curious user can contact Nikhar Abbas (nikhar.abbas@nrel.gov) for available models, if they do not have any themselves. +""" # Python Modules import os @@ -22,28 +22,31 @@ from rosco.toolbox import controller as ROSCO_controller from rosco.toolbox.inputs.validation import load_rosco_yaml - -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') - -# Load yaml file -parameter_filename = os.path.join(tune_dir,'BAR.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Load turbine data from openfast model -turbine = ROSCO_turbine.Turbine(turbine_params) -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']) - ) - -# Tune controller -controller = ROSCO_controller.Controller(controller_params) -controller.tune_controller(turbine) - -print('Flap PI gains:') -print('Kp_flap = {}'.format(controller.Kp_flap[-1])) -print('Ki_flap = {}'.format(controller.Ki_flap[-1])) +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + + # Load yaml file + parameter_filename = os.path.join(tune_dir,'BAR.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Load turbine data from openfast model + turbine = ROSCO_turbine.Turbine(turbine_params) + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']) + ) + + # Tune controller + controller = ROSCO_controller.Controller(controller_params) + controller.tune_controller(turbine) + + print('Flap PI gains:') + print('Kp_flap = {}'.format(controller.Kp_flap[-1])) + print('Ki_flap = {}'.format(controller.Ki_flap[-1])) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/Examples/10_linear_params.py b/Examples/10_linear_params.py index dc09aba6..95f9ff8a 100644 --- a/Examples/10_linear_params.py +++ b/Examples/10_linear_params.py @@ -1,14 +1,15 @@ -''' ------------ 10_linear_params -------------- -Load a turbine, tune a controller, export linear model -------------------------------------- +""" +10_linear_params +---------------- +Load a turbine, tune a controller, export linear model In this example: - - Load a turbine from OpenFAST - - Tune a controller - - Use tuning parameters to export linear model -''' +* Load a turbine from OpenFAST +* Tune a controller +* Use tuning parameters to export linear model +""" + # Python Modules import os # ROSCO toolbox modules @@ -16,69 +17,73 @@ from rosco.toolbox import turbine as ROSCO_turbine from rosco.toolbox.inputs.validation import load_rosco_yaml - import numpy as np -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') +def main(): + + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + + # Load yaml file + parameter_filename = os.path.join( tune_dir, 'IEA15MW.yaml') + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] -# Load yaml file -parameter_filename = os.path.join( tune_dir, 'IEA15MW.yaml') -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] + # Linear file output -# Linear file output + os.path.join(this_dir,path_params['FAST_directory']) + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) -os.path.join(this_dir,path_params['FAST_directory']) -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) + linmod_filename = os.path.join(example_out_dir,'10_IEA15MW_LinMod.dat') -linmod_filename = os.path.join(example_out_dir,'10_IEA15MW_LinMod.dat') + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) -# Instantiate turbine, controller, and file processing classes -turbine = ROSCO_turbine.Turbine(turbine_params) -controller = ROSCO_controller.Controller(controller_params) + # Load turbine data from OpenFAST and rotor performance text file + turbine.load_from_fast( + path_params['FAST_InputFile'], + os.path.join(tune_dir,path_params['FAST_directory']), + rot_source='txt', + txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) + ) -# Load turbine data from OpenFAST and rotor performance text file -turbine.load_from_fast( - path_params['FAST_InputFile'], - os.path.join(tune_dir,path_params['FAST_directory']), - rot_source='txt', - txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename']) - ) + # Tune controller + controller.tune_controller(turbine) -# Tune controller -controller.tune_controller(turbine) + # Write Linear model parameters to text file for matlab processing + # Add to ROSCO_utilities.FileProcessing() when finished -# Write Linear model parameters to text file for matlab processing -# Add to ROSCO_utilities.FileProcessing() when finished + print('Writing linear models to text file: ' + linmod_filename) -print('Writing linear models to text file: ' + linmod_filename) + # extend gain schedule -# extend gain schedule + pc_br = np.zeros(len(controller.v_below_rated)) + pc_Kp = np.concatenate((pc_br,controller.pc_gain_schedule.Kp)) + pc_Ki = np.concatenate((pc_br,controller.pc_gain_schedule.Ki)) -pc_br = np.zeros(len(controller.v_below_rated)) -pc_Kp = np.concatenate((pc_br,controller.pc_gain_schedule.Kp)) -pc_Ki = np.concatenate((pc_br,controller.pc_gain_schedule.Ki)) + vs_ar = np.zeros(len(controller.pc_gain_schedule.Kp)) + vs_Kp = np.concatenate((controller.vs_gain_schedule.Kp,vs_ar)) + vs_Ki = np.concatenate((controller.vs_gain_schedule.Ki,vs_ar)) -vs_ar = np.zeros(len(controller.pc_gain_schedule.Kp)) -vs_Kp = np.concatenate((controller.vs_gain_schedule.Kp,vs_ar)) -vs_Ki = np.concatenate((controller.vs_gain_schedule.Ki,vs_ar)) + with open(linmod_filename,'w') as f: + f.write('{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\n'.\ + format('WindSpeed','A_om','b_theta','b_tau','b_wind','pc_Kp','pc_Ki','vs_Kp','vs_Ki','Pi_omega','Pi_theta','Pi_wind')) -with open(linmod_filename,'w') as f: - f.write('{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\t{:12}\n'.\ - format('WindSpeed','A_om','b_theta','b_tau','b_wind','pc_Kp','pc_Ki','vs_Kp','vs_Ki','Pi_omega','Pi_theta','Pi_wind')) + for v,A,B_beta,B_tau,B_wind,pc_Kp,pc_Ki,vs_Kp,vs_Ki,Pi_omega,Pi_beta,Pi_wind in zip(controller.v,controller.A,controller.B_beta,controller.B_tau, controller.B_wind, \ + pc_Kp, pc_Ki, vs_Kp, vs_Ki, \ + controller.Pi_omega, controller.Pi_beta, controller.Pi_wind): + f.write('{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\n'\ + .format(v,A,B_beta,B_tau,B_wind,pc_Kp,pc_Ki,vs_Kp,vs_Ki,Pi_omega,Pi_beta,Pi_wind)) - for v,A,B_beta,B_tau,B_wind,pc_Kp,pc_Ki,vs_Kp,vs_Ki,Pi_omega,Pi_beta,Pi_wind in zip(controller.v,controller.A,controller.B_beta,controller.B_tau, controller.B_wind, \ - pc_Kp, pc_Ki, vs_Kp, vs_Ki, \ - controller.Pi_omega, controller.Pi_beta, controller.Pi_wind): - f.write('{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\t{:12.4e}\n'\ - .format(v,A,B_beta,B_tau,B_wind,pc_Kp,pc_Ki,vs_Kp,vs_Ki,Pi_omega,Pi_beta,Pi_wind)) + print('Tower Height = {} m'.format(turbine.hubHt)) + print('Platform Freq. = {} rad/s'.format(controller.ptfm_freq)) -print('Tower Height = {} m'.format(turbine.hubHt)) -print('Platform Freq. = {} rad/s'.format(controller.ptfm_freq)) +if __name__ == "__main__": + main() diff --git a/Examples/11_robust_tuning.py b/Examples/11_robust_tuning.py index 171968ae..d5c31fda 100644 --- a/Examples/11_robust_tuning.py +++ b/Examples/11_robust_tuning.py @@ -1,20 +1,22 @@ -''' ------------ 11_robust_tuning -------------- +""" +11_robust_tuning +---------------- Controller tuning to satisfy a robustness criteria -------------------------------------- -NOTE: This example necessitates the mbc3 through either pyFAST or WEIS + +Note that this example necessitates the mbc3 through either pyFAST or WEIS pyFAST is the easiest to install by cloning https://github.com/OpenFAST/openfast_toolbox and -running `python setup.py develop` from your conda environment +running ``python setup.py develop`` from your conda environment In this example: - - setup ROSCO's robust tuning methods for the IEA15MW on the UMaine Semi-sub - - run a the standard tuning method to find k_float - - run robust tuning to find omega_pc schedule satisfy a prescribed stability margin - - Tune ROSCO's pitch controller using omega_pc schedule - - Plot gain schedule + +* setup ROSCO's robust tuning methods for the IEA15MW on the UMaine Semi-sub +* run a the standard tuning method to find k_float +* run robust tuning to find omega_pc schedule satisfy a prescribed stability margin +* Tune ROSCO's pitch controller using omega_pc schedule +* Plot gain schedule The example is put in a function call to show the ability to load linear models in parallel -''' +""" import os import numpy as np import matplotlib.pyplot as plt diff --git a/Examples/12_tune_ipc.py b/Examples/12_tune_ipc.py index 6bb5b9e8..028407c8 100644 --- a/Examples/12_tune_ipc.py +++ b/Examples/12_tune_ipc.py @@ -1,14 +1,14 @@ -''' ------------ 12_tune_ipc -------------- +""" +12_tune_ipc +-------------- Load a turbine, tune a controller with IPC -------------------------------------- - In this example: - - Load a turbine from OpenFAST - - Tune a controller with IPC - - Run simple simulation with open loop control -''' +* Load a turbine from OpenFAST +* Tune a controller with IPC +* Run simple simulation with open loop control +""" + # Python Modules import os import matplotlib.pyplot as plt diff --git a/Examples/14_open_loop_control.py b/Examples/14_open_loop_control.py index 862b6306..2b895940 100644 --- a/Examples/14_open_loop_control.py +++ b/Examples/14_open_loop_control.py @@ -1,15 +1,15 @@ -''' ------------ 14_open_loop_control -------------- +""" +14_open_loop_control +-------------------- Load a turbine, tune a controller with open loop control commands -------------------------------------- - In this example: - - Load a turbine from OpenFAST - - Tune a controller - - Write open loop inputs - - Run simple simulation with open loop control -''' +* Load a turbine from OpenFAST +* Tune a controller +* Write open loop inputs +* Run simple simulation with open loop control +""" + # Python Modules import os import numpy as np @@ -26,138 +26,139 @@ from rosco.toolbox.ofTools.case_gen.runFAST_pywrapper import runFAST_pywrapper, runFAST_pywrapper_batch from rosco.toolbox.ofTools.case_gen.CaseGen_General import CaseGen_General - - -this_dir = os.path.dirname(os.path.abspath(__file__)) -tune_dir = os.path.join(this_dir,'Tune_Cases') - -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -example_out_dir = os.path.join(this_dir,'examples_out') -if not os.path.isdir(example_out_dir): - os.makedirs(example_out_dir) - -# Load yaml file (Open Loop Case) -parameter_filename = os.path.join(tune_dir, 'IEA15MW_OL.yaml') - -inps = load_rosco_yaml(parameter_filename) -path_params = inps['path_params'] -turbine_params = inps['turbine_params'] -controller_params = inps['controller_params'] - -# Set up open loop input -olc = ROSCO_controller.OpenLoopControl(t_max=20) -olc.interp_timeseries( - 'blade_pitch', - [0,20], - [0,0.0873] , - 'sigma' - ) -olc.const_timeseries( - 'generator_torque', - 19624046*.5 - ) -olc.sine_timeseries('nacelle_yaw', 0.0524, 60) - -# Plot open loop timeseries -fig,ax = olc.plot_timeseries() -if False: - plt.show() -else: - fig.savefig(os.path.join(example_out_dir,'14_OL_Inputs.png')) - -# Write open loop input, get OL indices -ol_filename = os.path.join(example_out_dir,'14_OL_Input.dat') -ol_dict = olc.write_input(ol_filename) -controller_params['open_loop'] = ol_dict - - -# Instantiate turbine, controller, and file processing classes -turbine = ROSCO_turbine.Turbine(turbine_params) -controller = ROSCO_controller.Controller(controller_params) - -# Load turbine data from OpenFAST and rotor performance text file -turbine.load_from_fast(path_params['FAST_InputFile'], \ - os.path.join(tune_dir,path_params['FAST_directory']), \ - rot_source='txt',\ - txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename'])) - -# Tune controller -controller.tune_controller(turbine) - -# Write parameter input file -param_file = os.path.join(this_dir,'DISCON.IN') # This must be named DISCON.IN to be seen by the compiled controller binary. -ROSCO_utilities.write_DISCON(turbine,controller,param_file=param_file, txt_filename=path_params['rotor_performance_filename']) - -### Run OpenFAST using aeroelasticse tools -case_inputs = {} -case_inputs[('ServoDyn','DLL_FileName')] = {'vals': [discon_lib_path], 'group': 0} - -# Apply all discon variables as case inputs -discon_vt = ROSCO_utilities.DISCON_dict( - turbine, -controller, -txt_filename=os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) -) -for discon_input in discon_vt: - case_inputs[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} - -case_inputs[('Fst','TMax')] = {'vals': [20], 'group': 0} -case_inputs[('InflowWind','HWindSpeed')] = {'vals': [10], 'group': 0} -case_inputs[('ElastoDyn','HWindSpeed')] = {'vals': [5.], 'group': 0} -case_inputs[('DISCON_in','LoggingLevel')] = {'vals': [3], 'group': 0} - -# Generate cases -run_dir = os.path.join(example_out_dir,'14_OL_Sim') -if not os.path.exists(run_dir): - os.makedirs(run_dir) - -case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=run_dir, namebase='OL_Example') -channels = set_channels() - -# Run FAST cases -fastBatch = runFAST_pywrapper_batch() - -fastBatch.FAST_directory = os.path.realpath(os.path.join(tune_dir,path_params['FAST_directory'])) -fastBatch.FAST_InputFile = path_params['FAST_InputFile'] -fastBatch.channels = channels -fastBatch.FAST_runDirectory = run_dir -fastBatch.case_list = case_list -fastBatch.case_name_list = case_name_list -fastBatch.debug_level = 2 -fastBatch.FAST_exe = 'openfast' - -fastBatch.run_serial() - - -# # Define Plot cases -cases = {} -cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed','NacYaw'] - -out_file = os.path.join(example_out_dir,'14_OL_Sim/OL_Example_0.outb') -op = output_processing.output_processing() -fastout = op.load_fast_out(out_file, tmin=0) -fig, ax = op.plot_fast_out(cases=cases,showplot=False) - -# Check that open loop commands are close to control outputs from OpenFAST -fo = fastout[0] -tt = fo['Time'] -valid_ind = tt > 2 # first few timesteps can differ, depending on OpenFAST solve config - -# Compute errors -nacelle_yaw_diff = fo['NacYaw'][valid_ind] - np.degrees(np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['nacelle_yaw'])) -bld_pitch_diff = fo['BldPitch1'][valid_ind] - np.degrees(np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['blade_pitch'])) -gen_tq_diff = fo['GenTq'][valid_ind] - np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['generator_torque'])/1e3 - -# Check diff timeseries -np.testing.assert_allclose(nacelle_yaw_diff, 0, atol = 1e-1) # yaw has dynamics and integration error, tolerance higher -np.testing.assert_allclose(bld_pitch_diff, 0, atol = 1e-3) -np.testing.assert_allclose(gen_tq_diff, 0, atol = 1e-3) - - -if False: - plt.show() -else: - fig[0].savefig(os.path.join(example_out_dir,'14_OL_FAST_Out.png')) - +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + tune_dir = os.path.join(this_dir,'Tune_Cases') + + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + example_out_dir = os.path.join(this_dir,'examples_out') + if not os.path.isdir(example_out_dir): + os.makedirs(example_out_dir) + + # Load yaml file (Open Loop Case) + parameter_filename = os.path.join(tune_dir, 'IEA15MW_OL.yaml') + + inps = load_rosco_yaml(parameter_filename) + path_params = inps['path_params'] + turbine_params = inps['turbine_params'] + controller_params = inps['controller_params'] + + # Set up open loop input + olc = ROSCO_controller.OpenLoopControl(t_max=20) + olc.interp_timeseries( + 'blade_pitch', + [0,20], + [0,0.0873] , + 'sigma' + ) + olc.const_timeseries( + 'generator_torque', + 19624046*.5 + ) + olc.sine_timeseries('nacelle_yaw', 0.0524, 60) + + # Plot open loop timeseries + fig,ax = olc.plot_timeseries() + if False: + plt.show() + else: + fig.savefig(os.path.join(example_out_dir,'14_OL_Inputs.png')) + + # Write open loop input, get OL indices + ol_filename = os.path.join(example_out_dir,'14_OL_Input.dat') + ol_dict = olc.write_input(ol_filename) + controller_params['open_loop'] = ol_dict + + + # Instantiate turbine, controller, and file processing classes + turbine = ROSCO_turbine.Turbine(turbine_params) + controller = ROSCO_controller.Controller(controller_params) + + # Load turbine data from OpenFAST and rotor performance text file + turbine.load_from_fast(path_params['FAST_InputFile'], \ + os.path.join(tune_dir,path_params['FAST_directory']), \ + rot_source='txt',\ + txt_filename=os.path.join(tune_dir,path_params['rotor_performance_filename'])) + + # Tune controller + controller.tune_controller(turbine) + + # Write parameter input file + param_file = os.path.join(this_dir,'DISCON.IN') # This must be named DISCON.IN to be seen by the compiled controller binary. + ROSCO_utilities.write_DISCON(turbine,controller,param_file=param_file, txt_filename=path_params['rotor_performance_filename']) + + ### Run OpenFAST using aeroelasticse tools + case_inputs = {} + case_inputs[('ServoDyn','DLL_FileName')] = {'vals': [discon_lib_path], 'group': 0} + + # Apply all discon variables as case inputs + discon_vt = ROSCO_utilities.DISCON_dict( + turbine, + controller, + txt_filename=os.path.join(tune_dir,path_params['FAST_directory'],path_params['rotor_performance_filename']) + ) + for discon_input in discon_vt: + case_inputs[('DISCON_in',discon_input)] = {'vals': [discon_vt[discon_input]], 'group': 0} + + case_inputs[('Fst','TMax')] = {'vals': [20], 'group': 0} + case_inputs[('InflowWind','HWindSpeed')] = {'vals': [10], 'group': 0} + case_inputs[('ElastoDyn','HWindSpeed')] = {'vals': [5.], 'group': 0} + case_inputs[('DISCON_in','LoggingLevel')] = {'vals': [3], 'group': 0} + + # Generate cases + run_dir = os.path.join(example_out_dir,'14_OL_Sim') + if not os.path.exists(run_dir): + os.makedirs(run_dir) + + case_list, case_name_list = CaseGen_General(case_inputs, dir_matrix=run_dir, namebase='OL_Example') + channels = set_channels() + + # Run FAST cases + fastBatch = runFAST_pywrapper_batch() + + fastBatch.FAST_directory = os.path.realpath(os.path.join(tune_dir,path_params['FAST_directory'])) + fastBatch.FAST_InputFile = path_params['FAST_InputFile'] + fastBatch.channels = channels + fastBatch.FAST_runDirectory = run_dir + fastBatch.case_list = case_list + fastBatch.case_name_list = case_name_list + fastBatch.debug_level = 2 + fastBatch.FAST_exe = 'openfast' + + fastBatch.run_serial() + + + # # Define Plot cases + cases = {} + cases['Baseline'] = ['Wind1VelX', 'BldPitch1', 'GenTq', 'RotSpeed','NacYaw'] + + out_file = os.path.join(example_out_dir,'14_OL_Sim/OL_Example_0.outb') + op = output_processing.output_processing() + fastout = op.load_fast_out(out_file, tmin=0) + fig, ax = op.plot_fast_out(cases=cases,showplot=False) + + # Check that open loop commands are close to control outputs from OpenFAST + fo = fastout[0] + tt = fo['Time'] + valid_ind = tt > 2 # first few timesteps can differ, depending on OpenFAST solve config + + # Compute errors + nacelle_yaw_diff = fo['NacYaw'][valid_ind] - np.degrees(np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['nacelle_yaw'])) + bld_pitch_diff = fo['BldPitch1'][valid_ind] - np.degrees(np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['blade_pitch'])) + gen_tq_diff = fo['GenTq'][valid_ind] - np.interp(tt[valid_ind],olc.ol_timeseries['time'],olc.ol_timeseries['generator_torque'])/1e3 + + # Check diff timeseries + np.testing.assert_allclose(nacelle_yaw_diff, 0, atol = 1e-1) # yaw has dynamics and integration error, tolerance higher + np.testing.assert_allclose(bld_pitch_diff, 0, atol = 1e-3) + np.testing.assert_allclose(gen_tq_diff, 0, atol = 1e-3) + + + if False: + plt.show() + else: + fig[0].savefig(os.path.join(example_out_dir,'14_OL_FAST_Out.png')) + +if __name__ == "__main__": + main() diff --git a/Examples/15_pass_through.py b/Examples/15_pass_through.py index c0b7916d..c8e2e4c5 100644 --- a/Examples/15_pass_through.py +++ b/Examples/15_pass_through.py @@ -1,27 +1,24 @@ -''' ------------ 15_pass_through -------------- +""" +15_pass_through +--------------- Use the runFAST scripts to set up an example, use pass through in yaml -------------------------------------- - In this example: - - use run_FAST_ROSCO class to set up a test case -''' +* use run_FAST_ROSCO class to set up a test case +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO from rosco.toolbox.ofTools.case_gen import CaseLibrary as cl - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): - # Simulation config + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) + + # Simulation config r = run_FAST_ROSCO() parameter_filename = os.path.join(this_dir,'Tune_Cases/NREL5MW_PassThrough.yaml') diff --git a/Examples/16_external_dll.py b/Examples/16_external_dll.py index cb79ab07..381f2623 100644 --- a/Examples/16_external_dll.py +++ b/Examples/16_external_dll.py @@ -1,11 +1,9 @@ -''' ------------ 16_external_dll -------------- +""" +16_external_dll +--------------- Run openfast with ROSCO and external control interface -------------------------------------- - IEA-15MW will call NREL-5MW controller and read control inputs - -''' +""" import os, platform from rosco import discon_lib_path as lib_name @@ -14,14 +12,12 @@ import shutil -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Make copy of libdiscon ext = lib_name.split('.')[-1] diff --git a/Examples/17a_zeromq_simple.py b/Examples/17a_zeromq_simple.py index 2ccae2be..e881964c 100644 --- a/Examples/17a_zeromq_simple.py +++ b/Examples/17a_zeromq_simple.py @@ -1,11 +1,11 @@ -''' ------------ 17_zeromq_interface -------------- -Run ROSCO using the ROSCO toolbox control interface and execute communication with ZeroMQ -------------------------------------- +""" +17a_zeromq_simple +-------------------- +Run ROSCO using the ROSCO toolbox control interface and execute communication with ZeroMQ. A demonstrator for ZeroMQ communication. Instead of using ROSCO with with control interface, one could call ROSCO from OpenFAST, and communicate with ZeroMQ through that. -''' +""" import os @@ -22,15 +22,22 @@ import numpy as np import multiprocessing as mp -TIME_CHECK = 30 -DESIRED_YAW_OFFSET = 20 -DESIRED_PITCH_OFFSET = np.deg2rad(2) * np.sin(0.1 * TIME_CHECK) + np.deg2rad(2) -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) +def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) + + logfile = os.path.join(example_out_dir,os.path.splitext(os.path.basename(__file__))[0]+'.log') + p1 = mp.Process(target=run_zmq,args=(logfile,)) + p1.start() + p2 = mp.Process(target=sim_rosco) + p2.start() + p1.join() + p2.join() + def run_zmq(logfile=None): # Start the server at the following address @@ -44,25 +51,29 @@ def run_zmq(logfile=None): server.runserver() def wfc_controller(id,current_time,measurements): - if current_time <= 10.0: - yaw_setpoint = 0.0 - else: - yaw_setpoint = DESIRED_YAW_OFFSET - - # Pitch offset - if current_time >= 10.0: - col_pitch_command = np.deg2rad(2) * np.sin(0.1 * current_time) + np.deg2rad(2) # Implement dynamic induction control - else: - col_pitch_command = 0.0 - - # Send new setpoints back to ROSCO - setpoints = {} - setpoints['ZMQ_TorqueOffset'] = 0.0 - setpoints['ZMQ_YawOffset'] = yaw_setpoint - setpoints['ZMQ_PitOffset(1)'] = col_pitch_command - setpoints['ZMQ_PitOffset(2)'] = col_pitch_command - setpoints['ZMQ_PitOffset(3)'] = col_pitch_command - return setpoints + time_check = 30 + desired_yaw_offset = 20 + desired_pitch_offset = np.deg2rad(2) * np.sin(0.1 * time_check) + np.deg2rad(2) + + if current_time <= 10.0: + yaw_setpoint = 0.0 + else: + yaw_setpoint = desired_yaw_offset + + # Pitch offset + if current_time >= 10.0: + col_pitch_command = np.deg2rad(2) * np.sin(0.1 * current_time) + np.deg2rad(2) # Implement dynamic induction control + else: + col_pitch_command = 0.0 + + # Send new setpoints back to ROSCO + setpoints = {} + setpoints['ZMQ_TorqueOffset'] = 0.0 + setpoints['ZMQ_YawOffset'] = yaw_setpoint + setpoints['ZMQ_PitOffset(1)'] = col_pitch_command + setpoints['ZMQ_PitOffset(2)'] = col_pitch_command + setpoints['ZMQ_PitOffset(3)'] = col_pitch_command + return setpoints def sim_rosco(): # Load yaml file @@ -157,10 +168,4 @@ def sim_rosco(): np.testing.assert_almost_equal(local_vars[0]['ZMQ_PitOffset'][ind_30], DESIRED_PITCH_OFFSET, decimal=3) if __name__ == "__main__": - logfile = os.path.join(example_out_dir,os.path.splitext(os.path.basename(__file__))[0]+'.log') - p1 = mp.Process(target=run_zmq,args=(logfile,)) - p1.start() - p2 = mp.Process(target=sim_rosco) - p2.start() - p1.join() - p2.join() + main() diff --git a/Examples/17b_zeromq_multi_openfast.py b/Examples/17b_zeromq_multi_openfast.py index 0de4d9a7..d3749696 100644 --- a/Examples/17b_zeromq_multi_openfast.py +++ b/Examples/17b_zeromq_multi_openfast.py @@ -1,3 +1,9 @@ +""" +17b_zeromq_multi_openfast +------------------------- +Run multiple openfast simulations and execute communication with ZeroMQ. +""" + import os import numpy as np import multiprocessing as mp @@ -7,13 +13,76 @@ from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO from rosco.toolbox.ofTools.fast_io import output_processing - -this_dir = os.path.dirname(os.path.abspath(__file__)) -example_out_dir = os.path.join(this_dir, "examples_out") -os.makedirs(example_out_dir, exist_ok=True) TIME_CHECK = 20 DESIRED_YAW_OFFSET = [-10, 10] +def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + example_out_dir = os.path.join(this_dir, "examples_out") + os.makedirs(example_out_dir, exist_ok=True) + + + # Start wind farm control server and two openfast simulation + # as separate processes + logfile = os.path.join(example_out_dir,os.path.splitext(os.path.basename(__file__))[0]+'.log') + p0 = mp.Process(target=run_zmq,args=(logfile,)) + p1 = mp.Process(target=sim_openfast_1) + p2 = mp.Process(target=sim_openfast_2) + + p0.start() + p1.start() + p2.start() + + p0.join() + p1.join() + p2.join() + + ## Run tests + # Check that info is passed to ROSCO for first simulation + op1 = output_processing.output_processing() + debug_file1 = os.path.join( + example_out_dir, + "17b_zeromq_OF1", + "NREL5MW", + "power_curve", + "base", + "NREL5MW_0.RO.dbg2", + ) + local_vars1 = op1.load_fast_out(debug_file1, tmin=0) + + # Check that info is passed to ROSCO for first simulation + op2 = output_processing.output_processing() + debug_file2 = os.path.join( + example_out_dir, + "17b_zeromq_OF2", + "NREL5MW", + "power_curve", + "base", + "NREL5MW_0.RO.dbg2", + ) + local_vars2 = op2.load_fast_out(debug_file2, tmin=0) + + # Generate plots + _, axs = plt.subplots(2, 1) + axs[0].plot(local_vars1[0]["Time"], local_vars1[0]["ZMQ_YawOffset"]) + axs[1].plot(local_vars2[0]["Time"], local_vars2[0]["ZMQ_YawOffset"]) + + if False: + plt.show() + else: + plt.savefig(os.path.join(example_out_dir, "17b_NREL5MW_ZMQ_Setpoints.png")) + + # Spot check input at time = 30 sec. + ind1_30 = local_vars1[0]["Time"] == TIME_CHECK + ind2_30 = local_vars2[0]["Time"] == TIME_CHECK + + np.testing.assert_almost_equal( + local_vars1[0]["ZMQ_YawOffset"][ind1_30], DESIRED_YAW_OFFSET[0] + ) + np.testing.assert_almost_equal( + local_vars2[0]["ZMQ_YawOffset"][ind2_30], DESIRED_YAW_OFFSET[1] + ) + def run_zmq(logfile=None): """Start the ZeroMQ server for wind farm control""" @@ -98,63 +167,4 @@ def sim_openfast_2(): if __name__ == "__main__": - # Start wind farm control server and two openfast simulation - # as separate processes - logfile = os.path.join(example_out_dir,os.path.splitext(os.path.basename(__file__))[0]+'.log') - p0 = mp.Process(target=run_zmq,args=(logfile,)) - p1 = mp.Process(target=sim_openfast_1) - p2 = mp.Process(target=sim_openfast_2) - - p0.start() - p1.start() - p2.start() - - p0.join() - p1.join() - p2.join() - - ## Run tests - # Check that info is passed to ROSCO for first simulation - op1 = output_processing.output_processing() - debug_file1 = os.path.join( - example_out_dir, - "17b_zeromq_OF1", - "NREL5MW", - "power_curve", - "base", - "NREL5MW_0.RO.dbg2", - ) - local_vars1 = op1.load_fast_out(debug_file1, tmin=0) - - # Check that info is passed to ROSCO for first simulation - op2 = output_processing.output_processing() - debug_file2 = os.path.join( - example_out_dir, - "17b_zeromq_OF2", - "NREL5MW", - "power_curve", - "base", - "NREL5MW_0.RO.dbg2", - ) - local_vars2 = op2.load_fast_out(debug_file2, tmin=0) - - # Generate plots - _, axs = plt.subplots(2, 1) - axs[0].plot(local_vars1[0]["Time"], local_vars1[0]["ZMQ_YawOffset"]) - axs[1].plot(local_vars2[0]["Time"], local_vars2[0]["ZMQ_YawOffset"]) - - if False: - plt.show() - else: - plt.savefig(os.path.join(example_out_dir, "17b_NREL5MW_ZMQ_Setpoints.png")) - - # Spot check input at time = 30 sec. - ind1_30 = local_vars1[0]["Time"] == TIME_CHECK - ind2_30 = local_vars2[0]["Time"] == TIME_CHECK - - np.testing.assert_almost_equal( - local_vars1[0]["ZMQ_YawOffset"][ind1_30], DESIRED_YAW_OFFSET[0] - ) - np.testing.assert_almost_equal( - local_vars2[0]["ZMQ_YawOffset"][ind2_30], DESIRED_YAW_OFFSET[1] - ) + main() \ No newline at end of file diff --git a/Examples/18_pitch_offsets.py b/Examples/18_pitch_offsets.py index 74f42201..baaf8ab2 100644 --- a/Examples/18_pitch_offsets.py +++ b/Examples/18_pitch_offsets.py @@ -1,11 +1,9 @@ -''' ------------ 18_pitch_offsets ------------------------ +""" +18_pitch_offsets +---------------- Run openfast with ROSCO and pitch offset faults ------------------------------------------------ - Set up and run simulation with pitch offsets, check outputs - -''' +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -14,14 +12,12 @@ import numpy as np -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW.yaml') diff --git a/Examples/19_update_discon_version.py b/Examples/19_update_discon_version.py index e4cd2960..78feb5d0 100644 --- a/Examples/19_update_discon_version.py +++ b/Examples/19_update_discon_version.py @@ -1,17 +1,16 @@ -''' ------------ 19_update_discon_version ----------------- +""" +19_update_discon_version +------------------------ Test and demonstrate update_discon_version() function for converting an old ROSCO input to the current version -''' +""" import os from rosco.toolbox.tune import update_discon_version -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) - - def main(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) old_discon_filename = os.path.join(this_dir,'example_inputs','DISCON_v2.2.0.IN') # An IEA-15MW input @@ -23,7 +22,5 @@ def main(): os.path.join(this_dir,'examples_out','18_UPDATED_DISCON.IN') ) - - if __name__ == "__main__": main() \ No newline at end of file diff --git a/Examples/20_active_wake_control.py b/Examples/20_active_wake_control.py index aa936a9f..85fca9d8 100644 --- a/Examples/20_active_wake_control.py +++ b/Examples/20_active_wake_control.py @@ -1,157 +1,157 @@ -''' ------------ 20_active_wake_control ------------ +""" +20_active_wake_control +---------------------- Run openfast with ROSCO and active wake control ------------------------------------------------ Set up and run simulation with AWC, check outputs Active wake control (AWC) with blade pitching is implemented in this example with two approaches as detailed below: - ------------------------------------------------ -AWC_Mode = 1: Normal mode method: ------------------------------------------------ -The normal mode method is an adaptation into the rotating frame of the mathematical framework from the classical theory for stability of axisymmetric jets [1], which offers flexibility in specifying the forcing strategy. - -The inputs to the controller are: - Name Unit Type Range Description - AWC_NumModes - Integer [0,inf] number of forcing modes - AWC_n - Integer [-inf,inf] azimuthal mode number(s) (i.e., the azimuthal mode number relates to the number and direction of the lobes of the wake structure according to the classical spatio-temporal Fourier decomposition of an arbitrary quantity q, sigma{sigma{q*exp(i*n*theta)*exp(i*omega*time)}}. For the case of a non-time-varying flow (i.e., where omega = 0), the azimuthal mode number specifies the number of cycles of blade pitch oscillation per one rotation around the rotor azimuth.) - AWC_clockangle deg Float [0,360] clocking angle(s) of forcing mode(s) - AWC_freq Hz Float [0,inf] frequency(s) of forcing mode(s) - AWC_amp deg Float [0,inf] pitch amplitude(s) of forcing mode(s) (note that AWC_amp specifies the amplitude of each individual mode so that the total amplitude of pitching will be the sum of AWC_amp) - -The latter two inputs may be specified based on the expected inflow while the former three inputs determine the type of active wake control to be used. - -Readers may be familiar with several forcing strategies from literature on active wake control that can be represented as follows: - -collective dynamic induction control: AWC_NumModes = 1, AWC_n = 0, AWC_clockangle = 0 - -helix clockwise [2]: AWC_NumModes = 1, AWC_n = 1, AWC_clockangle = 0 - -helix counter-clockwise [2]: AWC_NumModes = 1, AWC_n = -1, AWC_clockangle = 0 - -up-and-down: AWC_NumModes = 2, AWC_n = -1 1, AWC_clockangle = 0 0 - -side-to-side: AWC_NumModes = 2, AWC_n = -1 1, AWC_clockangle = 90 90 - -other: Higher-order modes or different combinations of the above can also be specified +""" +# ----------------------------------------------- +# AWC_Mode = 1: Normal mode method: +# ----------------------------------------------- +# The normal mode method is an adaptation into the rotating frame of the mathematical framework from the classical theory for stability of axisymmetric jets [1], which offers flexibility in specifying the forcing strategy. + +# The inputs to the controller are: +# Name Unit Type Range Description +# AWC_NumModes - Integer [0,inf] number of forcing modes +# AWC_n - Integer [-inf,inf] azimuthal mode number(s) (i.e., the azimuthal mode number relates to the number and direction of the lobes of the wake structure according to the classical spatio-temporal Fourier decomposition of an arbitrary quantity q, sigma{sigma{q*exp(i*n*theta)*exp(i*omega*time)}}. For the case of a non-time-varying flow (i.e., where omega = 0), the azimuthal mode number specifies the number of cycles of blade pitch oscillation per one rotation around the rotor azimuth.) +# AWC_clockangle deg Float [0,360] clocking angle(s) of forcing mode(s) +# AWC_freq Hz Float [0,inf] frequency(s) of forcing mode(s) +# AWC_amp deg Float [0,inf] pitch amplitude(s) of forcing mode(s) (note that AWC_amp specifies the amplitude of each individual mode so that the total amplitude of pitching will be the sum of AWC_amp) + +# The latter two inputs may be specified based on the expected inflow while the former three inputs determine the type of active wake control to be used. + +# Readers may be familiar with several forcing strategies from literature on active wake control that can be represented as follows: +# -collective dynamic induction control: AWC_NumModes = 1, AWC_n = 0, AWC_clockangle = 0 +# -helix clockwise [2]: AWC_NumModes = 1, AWC_n = 1, AWC_clockangle = 0 +# -helix counter-clockwise [2]: AWC_NumModes = 1, AWC_n = -1, AWC_clockangle = 0 +# -up-and-down: AWC_NumModes = 2, AWC_n = -1 1, AWC_clockangle = 0 0 +# -side-to-side: AWC_NumModes = 2, AWC_n = -1 1, AWC_clockangle = 90 90 +# -other: Higher-order modes or different combinations of the above can also be specified - These strategies are implemented using the following calculation methodology: - For each blade, we compute the total phase angle of blade pitch excursion according to: - AWC_angle(t) = 2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180) (eq 1) - where t is time - phi(t) is the angular offset of the given blade in the rotor plane relative to blade 1 - psi is the angle of blade 1 in the rotor plane from top-dead center +# These strategies are implemented using the following calculation methodology: +# For each blade, we compute the total phase angle of blade pitch excursion according to: +# AWC_angle(t) = 2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180) (eq 1) +# where t is time +# phi(t) is the angular offset of the given blade in the rotor plane relative to blade 1 +# psi is the angle of blade 1 in the rotor plane from top-dead center - Next, the phase angle is converted into the complex pitch amplitude: - AWC_complexangle(t) = AWC_amp*PI/180 * EXP(i * AWC_angle(t)) (eq 2) - where i is the square root of -1 +# Next, the phase angle is converted into the complex pitch amplitude: +# AWC_complexangle(t) = AWC_amp*PI/180 * EXP(i * AWC_angle(t)) (eq 2) +# where i is the square root of -1 - Note that if AWC_NumModes>1, then eq 1 and 2 are computed for each additional mode, and AWC_complexangle becomes a summation over all modes for each blade. +# Note that if AWC_NumModes>1, then eq 1 and 2 are computed for each additional mode, and AWC_complexangle becomes a summation over all modes for each blade. - Finally, the real pitch amplitude, theta(t), to be passed to the next step of the controller is calculated: - theta(t) = theta_0(t) + REAL(AWC_complexangle(t)) (eq 3) - where theta_0(t) is the controller's nominal pitch command +# Finally, the real pitch amplitude, theta(t), to be passed to the next step of the controller is calculated: +# theta(t) = theta_0(t) + REAL(AWC_complexangle(t)) (eq 3) +# where theta_0(t) is the controller's nominal pitch command - Rearranging for ease of viewing: - Inserting eq 1 into eq 2, and then putting that result into eq 3 gives: - theta(t) = theta_0(t) + REAL(AWC_amp*PI/180 * EXP(i * (2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180)))) (eq 4) +# Rearranging for ease of viewing: +# Inserting eq 1 into eq 2, and then putting that result into eq 3 gives: +# theta(t) = theta_0(t) + REAL(AWC_amp*PI/180 * EXP(i * (2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180)))) (eq 4) - Applying Euler's formula and carrying out the REAL operator: - theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180)) (eq 5) +# Applying Euler's formula and carrying out the REAL operator: +# theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t - AWC_n * (psi(t) + phi + AWC_clockangle*PI/180)) (eq 5) - As an example, we can set parameters to produce the counter-clockwise helix pattern from [2] using AWC_NumModes = 1, AWC_n = -1, and AWC_clockangle = 0: - For blade 1, eq 5 becomes: - theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t + psi(t)) (eq 6) +# As an example, we can set parameters to produce the counter-clockwise helix pattern from [2] using AWC_NumModes = 1, AWC_n = -1, and AWC_clockangle = 0: +# For blade 1, eq 5 becomes: +# theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t + psi(t)) (eq 6) -Note that the inverse multi-blade coordinate (MBC) transformation can also be used to obtain the same result as eq 6. - Beginning with Eq. 3 from [2], we have +# Note that the inverse multi-blade coordinate (MBC) transformation can also be used to obtain the same result as eq 6. +# Beginning with Eq. 3 from [2], we have - / \ / \ - | theta_1(t) | | theta_0(t) | - | theta_2(t) | = T^-1(psi(t)) * | theta_tilt(t) | (eq 7) - | theta_3(t) | | theta_yaw(t) | - \ / \ / +# / \ / \ +# | theta_1(t) | | theta_0(t) | +# | theta_2(t) | = T^-1(psi(t)) * | theta_tilt(t) | (eq 7) +# | theta_3(t) | | theta_yaw(t) | +# \ / \ / - where +# where - / \ - | 1 cos(psi_1(t)) sin(psi_1(t)) | - T^-1(psi(t)) = | 1 cos(psi_2(t)) sin(psi_2(t)) | - | 1 cos(psi_3(t)) sin(psi_3(t)) | - \ / +# / \ +# | 1 cos(psi_1(t)) sin(psi_1(t)) | +# T^-1(psi(t)) = | 1 cos(psi_2(t)) sin(psi_2(t)) | +# | 1 cos(psi_3(t)) sin(psi_3(t)) | +# \ / - Multiplying the first row of the top matrix (and dropping the subscript of blade 1) yields: - theta(t) = theta_0(t) + theta_tilt(t)*cos(psi(t)) + theta_yaw(t)*sin(psi(t)) (eq 8) +# Multiplying the first row of the top matrix (and dropping the subscript of blade 1) yields: +# theta(t) = theta_0(t) + theta_tilt(t)*cos(psi(t)) + theta_yaw(t)*sin(psi(t)) (eq 8) - Setting theta_tilt(t) = AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t) and theta_yaw(t) = -AWC_amp*PI/180 * sin(2*Pi*AWC_freq * t) gives: - theta(t) = theta_0(t) + (AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t))*cos(psi(t)) - (AWC_amp*PI/180 * sin(2*Pi*AWC_freq * t))*sin(psi(t)) (eq 9) +# Setting theta_tilt(t) = AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t) and theta_yaw(t) = -AWC_amp*PI/180 * sin(2*Pi*AWC_freq * t) gives: +# theta(t) = theta_0(t) + (AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t))*cos(psi(t)) - (AWC_amp*PI/180 * sin(2*Pi*AWC_freq * t))*sin(psi(t)) (eq 9) - Applying a Ptolemy identity gives: - theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t + psi(t)) (eq 10) - which is equivlanet to eq 6 above. - ------------------------------------------------ -AWC_Mode = 2: Coleman transform method: ------------------------------------------------ -A second method is the Coleman transform method. - -The inputs to the controller are: - Name Unit Type Range Description - AWC_NumModes - Integer [1,2] number of modes for tilt and yaw (1: identical settings for tilt and yaw pitch angles, 2: seperate settings for tilt and yaw moments) - AWC_harmonic - Integer [0,inf] harmonic(s) to apply in the inverse Coleman transform (size = AWC_NumModes. 0: collective pitch AWC, 1: 1P IPC-AWC, 2: 2P IPC-AWC, etc.) - AWC_clockangle deg Array of Floats [-360,360] clocking angle(s) of tilt and yaw pitch angles (size = AWC_NumModes. If size = 1, yaw clockangle = 2*clockangle) - AWC_freq Hz Array of Floats [0,inf] frequency(s) of the tilt and yaw ptich angles, respectively (size = AWC_NumModes. If size = 1, both frequencies are assumed identical) - AWC_amp deg Array of Floats [0,inf] pitch amplitude(s) of tilt and yaw pitch angles (size = AWC_NumModes. If size = 1, both amplitudes are assumed identical) +# Applying a Ptolemy identity gives: +# theta(t) = theta_0(t) + AWC_amp*PI/180 * cos(2*Pi*AWC_freq * t + psi(t)) (eq 10) +# which is equivlanet to eq 6 above. + +# ----------------------------------------------- +# AWC_Mode = 2: Coleman transform method: +# ----------------------------------------------- +# A second method is the Coleman transform method. + +# The inputs to the controller are: +# Name Unit Type Range Description +# AWC_NumModes - Integer [1,2] number of modes for tilt and yaw (1: identical settings for tilt and yaw pitch angles, 2: seperate settings for tilt and yaw moments) +# AWC_harmonic - Integer [0,inf] harmonic(s) to apply in the inverse Coleman transform (size = AWC_NumModes. 0: collective pitch AWC, 1: 1P IPC-AWC, 2: 2P IPC-AWC, etc.) +# AWC_clockangle deg Array of Floats [-360,360] clocking angle(s) of tilt and yaw pitch angles (size = AWC_NumModes. If size = 1, yaw clockangle = 2*clockangle) +# AWC_freq Hz Array of Floats [0,inf] frequency(s) of the tilt and yaw ptich angles, respectively (size = AWC_NumModes. If size = 1, both frequencies are assumed identical) +# AWC_amp deg Array of Floats [0,inf] pitch amplitude(s) of tilt and yaw pitch angles (size = AWC_NumModes. If size = 1, both amplitudes are assumed identical) -Using the inputs mentioned above, the user is able to specify any desired combination of sinusoidal tilt and yaw modes to be tracked by the turbine. -When a single mode is defined in the inputs, the prescribed tilt and yaw angles are assumed to be identical, except for the phase. The phase difference -between the tilt and yaw angles is taken from the input AWC_clockangle. - -Readers may be familiar with several forcing strategies from literature on active wake control that can be represented as follows: - -collective dynamic induction control: AWC_NumModes = 1, AWC_harmonic = 0 - -helix clockwise [2]: AWC_NumModes = 1, AWC_harmonic = 1, AWC_clockangle = -90 OR AWC_NumModes = 2, AWC_n = [1 1], AWC_clockangle = [0 -90] - -helix counter-clockwise [2]: AWC_NumModes = 1, AWC_harmonic = 1, AWC_clockangle = 90 OR AWC_NumModes = 2, AWC_n = [1 1], AWC_clockangle = [0 90] - -up-and-down: AWC_NumModes = 2, AWC_harmonic = [1 1], AWC_amp = [# 0] (where "#" represents the desired amplitude) - -side-to-side: AWC_NumModes = 2, AWC_harmonic = [1 1], AWC_amp = [0 #] (where "#" represents the desired amplitude) - -other: different combinations of the above can also be specified +# Using the inputs mentioned above, the user is able to specify any desired combination of sinusoidal tilt and yaw modes to be tracked by the turbine. +# When a single mode is defined in the inputs, the prescribed tilt and yaw angles are assumed to be identical, except for the phase. The phase difference +# between the tilt and yaw angles is taken from the input AWC_clockangle. + +# Readers may be familiar with several forcing strategies from literature on active wake control that can be represented as follows: +# -collective dynamic induction control: AWC_NumModes = 1, AWC_harmonic = 0 +# -helix clockwise [2]: AWC_NumModes = 1, AWC_harmonic = 1, AWC_clockangle = -90 OR AWC_NumModes = 2, AWC_n = [1 1], AWC_clockangle = [0 -90] +# -helix counter-clockwise [2]: AWC_NumModes = 1, AWC_harmonic = 1, AWC_clockangle = 90 OR AWC_NumModes = 2, AWC_n = [1 1], AWC_clockangle = [0 90] +# -up-and-down: AWC_NumModes = 2, AWC_harmonic = [1 1], AWC_amp = [# 0] (where "#" represents the desired amplitude) +# -side-to-side: AWC_NumModes = 2, AWC_harmonic = [1 1], AWC_amp = [0 #] (where "#" represents the desired amplitude) +# -other: different combinations of the above can also be specified - These strategies are implemented using the following calculation methodology: - The inputs described above enable the user to specify a desired sinusoidal signal for either the collective pitch (AWC_n = 0) or tilt and yaw pitch - angles (AWC_n = 1). These AWC pitch angles are defined as: - AWC_angle(t) = AWC_amp * sin(2*pi*AWC_freq*t + AWC_clockangle) (eq 1) +# These strategies are implemented using the following calculation methodology: +# The inputs described above enable the user to specify a desired sinusoidal signal for either the collective pitch (AWC_n = 0) or tilt and yaw pitch +# angles (AWC_n = 1). These AWC pitch angles are defined as: +# AWC_angle(t) = AWC_amp * sin(2*pi*AWC_freq*t + AWC_clockangle) (eq 1) - In case of collective pitch AWC, this signal is directly superimposed on the regular pitch control signal. +# In case of collective pitch AWC, this signal is directly superimposed on the regular pitch control signal. - In case of IPC-based AWC, the reference tilt and yaw pitch angles theta are transformed to the rotating frame (i.e., pitch angles theta_k(t) for all - individual blades) using the inverse MBC transformation: +# In case of IPC-based AWC, the reference tilt and yaw pitch angles theta are transformed to the rotating frame (i.e., pitch angles theta_k(t) for all +# individual blades) using the inverse MBC transformation: - / \ / \ - | theta_1(t) | | theta_0(t) | - | theta_2(t) | = T^-1(psi(t)) * | theta_tilt(t) | (eq 2) - | theta_3(t) | | theta_yaw(t) | - \ / \ / +# / \ / \ +# | theta_1(t) | | theta_0(t) | +# | theta_2(t) | = T^-1(psi(t)) * | theta_tilt(t) | (eq 2) +# | theta_3(t) | | theta_yaw(t) | +# \ / \ / - where +# where - theta_tilt(t) = AWC_amp(1) * sin(2*pi*AWC_freq(1)*t + AWC_clockangle(1)) (eq 3) - theta_yaw(t) = AWC_amp(2) * sin(2*pi*AWC_freq(2)*t + AWC_clockangle(2)) (eq 4) +# theta_tilt(t) = AWC_amp(1) * sin(2*pi*AWC_freq(1)*t + AWC_clockangle(1)) (eq 3) +# theta_yaw(t) = AWC_amp(2) * sin(2*pi*AWC_freq(2)*t + AWC_clockangle(2)) (eq 4) - and - / \ - | 1 cos(psi_1(t)) sin(psi_1(t)) | - T^-1(psi(t)) = | 1 cos(psi_2(t)) sin(psi_2(t)) | (eq 5) - | 1 cos(psi_3(t)) sin(psi_3(t)) | - \ / +# and +# / \ +# | 1 cos(psi_1(t)) sin(psi_1(t)) | +# T^-1(psi(t)) = | 1 cos(psi_2(t)) sin(psi_2(t)) | (eq 5) +# | 1 cos(psi_3(t)) sin(psi_3(t)) | +# \ / - with psi_k(t) the azimuthal position of blade k at time instant t. Note that if AWC_NumModes = 1, it is assumed that: - AWC_amp(2) = AWC_amp(1) - AWC_freq(2) = AWC_freq(1) - AWC_clockangle(2) = 2*AWC_clockangle(1) +# with psi_k(t) the azimuthal position of blade k at time instant t. Note that if AWC_NumModes = 1, it is assumed that: +# AWC_amp(2) = AWC_amp(1) +# AWC_freq(2) = AWC_freq(1) +# AWC_clockangle(2) = 2*AWC_clockangle(1) - For more information on this control strategy, the user is referred to [2]. +# For more information on this control strategy, the user is referred to [2]. ------------------------------------------------ +# ----------------------------------------------- + +# General Implementation note: AWC strategies will be compromised if the AWC pitch command attempts to lower the blade pitch below value PC_MinPit as specified +# in the DISCON file, so PC_MinPit may need to be reduced by the user. -General Implementation note: AWC strategies will be compromised if the AWC pitch command attempts to lower the blade pitch below value PC_MinPit as specified -in the DISCON file, so PC_MinPit may need to be reduced by the user. +# References: +# [1] - Batchelor, G. K., and A. E. Gill. "Analysis of the stability of axisymmetric jets." Journal of fluid mechanics 14.4 (1962): 529-551. +# [2] - Frederik, Joeri A., et al. "The helix approach: Using dynamic individual pitch control to enhance wake mixing in wind farms." Wind Energy 23.8 (2020): 1739-1751. -References: -[1] - Batchelor, G. K., and A. E. Gill. "Analysis of the stability of axisymmetric jets." Journal of fluid mechanics 14.4 (1962): 529-551. -[2] - Frederik, Joeri A., et al. "The helix approach: Using dynamic individual pitch control to enhance wake mixing in wind farms." Wind Energy 23.8 (2020): 1739-1751. -''' import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -160,20 +160,17 @@ from rosco.toolbox.utilities import read_DISCON #, DISCON_dict #import numpy as np -# Choose your implementation method -AWC_Mode = 1 # 1 for SNL implementation, 2 for Coleman Transformation implementation - - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - - def main(): + # Choose your implementation method + AWC_Mode = 1 # 1 for SNL implementation, 2 for Coleman Transformation implementation + + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) + # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/NREL2p8.yaml') # will be dummy and overwritten with SNL DISCON params run_dir = os.path.join(example_out_dir,'20_active_wake_control/all_cases') diff --git a/Examples/21_optional_inputs.py b/Examples/21_optional_inputs.py index 17b4f90b..59292fc1 100644 --- a/Examples/21_optional_inputs.py +++ b/Examples/21_optional_inputs.py @@ -1,8 +1,9 @@ -''' ------------ 21_optional_inputse_discon_version ----------------- +""" +21_optional_inputse_discon_version +---------------------------------- Test and demonstrate update_discon_version() function for converting an old ROSCO input to the current version -''' +""" import os from rosco import discon_lib_path @@ -11,15 +12,12 @@ from rosco.toolbox import turbine as ROSCO_turbine #import numpy as np -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -example_in_dir = os.path.join(this_dir,'example_inputs') -os.makedirs(example_out_dir,exist_ok=True) - - -def main(): +def main():#directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + example_in_dir = os.path.join(this_dir,'example_inputs') + os.makedirs(example_out_dir,exist_ok=True) # Load turbine model from saved pickle turbine = ROSCO_turbine.Turbine diff --git a/Examples/22_cable_control.py b/Examples/22_cable_control.py index ce9a85b2..e5e23885 100644 --- a/Examples/22_cable_control.py +++ b/Examples/22_cable_control.py @@ -1,11 +1,18 @@ -''' ------------ 22_cable_control ------------------------ +""" +22_cable_control +---------------- Run openfast with ROSCO and cable control ------------------------------------------------ - Set up and run simulation with pitch offsets, check outputs -''' +ROSCO currently supports user-defined hooks for cable control actuation, if CC_Mode = 1. +The control logic can be determined in Controllers.f90 with the CableControl subroutine. +The CableControl subroutine takes an array of CC_DesiredL (length) equal to the ChannelIDs set in MoorDyn and +determines the length and change in length needed for MoorDyn using a 2nd order actuator model (CC\_ActTau). +In the DISCON input, users must specify CC\_GroupIndex relating to the deltaL of each control ChannelID. +These indices can be found in the ServoDyn summary file (\*SrvD.sum) + +In the example below (and hard-coded in ROSCO) a step change of -10 m on line 1 is applied at 50 sec. +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -17,25 +24,12 @@ import matplotlib.pyplot as plt from rosco.toolbox.controller import OpenLoopControl -''' -ROSCO currently supports user-defined hooks for cable control actuation, if CC_Mode = 1. -The control logic can be determined in Controllers.f90 with the CableControl subroutine. -The CableControl subroutine takes an array of CC_DesiredL (length) equal to the ChannelIDs set in MoorDyn and -determines the length and change in length needed for MoorDyn using a 2nd order actuator model (CC_ActTau). -In the DISCON input, users must specify CC_GroupIndex relating to the deltaL of each control ChannelID. -These indices can be found in the ServoDyn summary file (*SrvD.sum) - -In the example below (and hard-coded in ROSCO) a step change of -10 m on line 1 is applied at 50 sec. -''' - - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW_cable.yaml') diff --git a/Examples/23_structural_control.py b/Examples/23_structural_control.py index 63022907..96569c93 100644 --- a/Examples/23_structural_control.py +++ b/Examples/23_structural_control.py @@ -1,11 +1,19 @@ -''' ------------ 23_structural_control ------------------------ +""" +23_structural_control +--------------------- Run openfast with ROSCO and structural control ------------------------------------------------ - Set up and run simulation with pitch offsets, check outputs -''' +ROSCO currently supports user-defined hooks for structural control control actuation, if StC\_Mode = 1. +The control logic can be determined in Controllers.f90 with the StructrualControl subroutine. +In the DISCON input, users must specify StC\_GroupIndex relating to the control ChannelID. +These indices can be found in the ServoDyn summary file (\*SrvD.sum) + +In the example below, we implement a smooth step change mimicing the exchange of ballast from the +upwind column to the down wind columns + +OpenFAST v3.5.0 is required to run this example +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -15,28 +23,12 @@ from rosco.toolbox.inputs.validation import load_rosco_yaml from rosco.toolbox.controller import OpenLoopControl - -''' -ROSCO currently supports user-defined hooks for structural control control actuation, if StC_Mode = 1. -The control logic can be determined in Controllers.f90 with the StructrualControl subroutine. -In the DISCON input, users must specify StC_GroupIndex relating to the control ChannelID. -These indices can be found in the ServoDyn summary file (*SrvD.sum) - -In the example below, we implement a smooth step change mimicing the exchange of ballast from the -upwind column to the down wind columns - -OpenFAST v3.5.0 is required to run this example -''' - - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW_ballast.yaml') diff --git a/Examples/24_floating_feedback.py b/Examples/24_floating_feedback.py index 7da30c70..db895c18 100644 --- a/Examples/24_floating_feedback.py +++ b/Examples/24_floating_feedback.py @@ -1,16 +1,14 @@ -''' ------------ 23_structural_control ------------------------ +""" +24_floating_feedback +--------------------- Run openfast with ROSCO and all the floating feedback methods ------------------------------------------------ - Floating feedback methods available in ROSCO/ROSCO_Toolbox -1. Automated tuning, constant for all wind speeds -2. Automated tuning, varies with wind speed -3. Direct tuning, constant for all wind speeds -4. Direct tuning, varies with wind speeds - -''' +#. Automated tuning, constant for all wind speeds +#. Automated tuning, varies with wind speed +#. Direct tuning, constant for all wind speeds +#. Direct tuning, varies with wind speeds +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -26,14 +24,12 @@ import matplotlib.pyplot as plt - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW.yaml') diff --git a/Examples/25_rotor_position_control.py b/Examples/25_rotor_position_control.py index fe1abef8..d1449d59 100644 --- a/Examples/25_rotor_position_control.py +++ b/Examples/25_rotor_position_control.py @@ -1,11 +1,9 @@ -''' ------------ 25_rotor_position_control -------------- +""" +25_rotor_position_control +------------------------- Run ROSCO with rotor position control -------------------------------------- - Run a steady simulation, use the azimuth output as an input to the next steady simulation, with different ICs - -''' +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -17,14 +15,12 @@ import matplotlib.pyplot as plt -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Set up paths diff --git a/Examples/26_marine_hydro.py b/Examples/26_marine_hydro.py index 6773482b..24abfc3f 100644 --- a/Examples/26_marine_hydro.py +++ b/Examples/26_marine_hydro.py @@ -1,10 +1,8 @@ -''' ------------ 26_marine_hydro ------------------------ -Run openfast with ROSCO and a MHK turbine ------------------------------------------------ - - -''' +""" +26_marine_hydro +--------------- +Run MHK turbine in OpenFAST with ROSCO torque controller +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -15,20 +13,12 @@ #import matplotlib.pyplot as plt #from rosco.toolbox.controller import OpenLoopControl -''' -Run MHK turbine in OpenFAST with ROSCO torque controller - - -''' - - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - def main(): + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/RM1_MHK.yaml') diff --git a/Examples/27_power_ref_control.py b/Examples/27_power_ref_control.py index ebeba3b6..d6164234 100644 --- a/Examples/27_power_ref_control.py +++ b/Examples/27_power_ref_control.py @@ -1,12 +1,10 @@ -''' ------------ 27_power_ref_control ------------------------ +""" +27_power_ref_control +-------------------- Run openfast with ROSCO and cable control ------------------------------------------------ - Demonstrate a simulation with a generator reference speed that changes with estimated wind speed - - -''' +Set reference rotor speed as a function of wind speed (estimate in ROSCO) +""" import os from rosco import discon_lib_path @@ -17,24 +15,16 @@ #from rosco.toolbox.inputs.validation import load_rosco_yaml import matplotlib.pyplot as plt -''' -Set reference rotor speed as a function of wind speed (estimate in ROSCO) - -''' - FULL_TEST = False - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) -lib_name = discon_lib_path - - def main(): - + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) + lib_name = discon_lib_path + # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW.yaml') run_dir = os.path.join(example_out_dir,'27_PRC_0') diff --git a/Examples/28_tower_resonance.py b/Examples/28_tower_resonance.py index 912e4081..209a928e 100644 --- a/Examples/28_tower_resonance.py +++ b/Examples/28_tower_resonance.py @@ -1,11 +1,9 @@ -''' ------------ 28_tower_resonance ------------------------ +""" +28_tower_resonance +------------------ Demonstrate tower resonance avoidance controller ------------------------------------------------ - Set up and run simulation with tower resonance avoidance - -''' +""" import os from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO @@ -16,17 +14,14 @@ import numpy as np -rpm2RadSec = 2.0*(np.pi)/60.0 - - -#directories -this_dir = os.path.dirname(os.path.abspath(__file__)) -rosco_dir = os.path.dirname(this_dir) -example_out_dir = os.path.join(this_dir,'examples_out') -os.makedirs(example_out_dir,exist_ok=True) - - def main(): + rpm2RadSec = 2.0*(np.pi)/60.0 + + #directories + this_dir = os.path.dirname(os.path.abspath(__file__)) + rosco_dir = os.path.dirname(this_dir) + example_out_dir = os.path.join(this_dir,'examples_out') + os.makedirs(example_out_dir,exist_ok=True) # Input yaml and output directory parameter_filename = os.path.join(this_dir,'Tune_Cases/IEA15MW.yaml') diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 9bb5f04c..ce1b73f1 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -104,3 +104,30 @@ List of Examples A complete list of examples is given below: .. automodule:: 01_turbine_model +.. automodule:: 02_ccblade +.. automodule:: 03_tune_controller +.. automodule:: 04_simple_sim +.. automodule:: 05_openfast_sim +.. automodule:: 06_peak_shaving +.. automodule:: 07_openfast_outputs +.. automodule:: 08_run_turbsim +.. automodule:: 09_distributed_aero +.. automodule:: 10_linear_params +.. automodule:: 11_robust_tuning +.. automodule:: 12_tune_ipc +.. automodule:: 14_open_loop_control +.. automodule:: 15_pass_through +.. automodule:: 16_external_dll +.. automodule:: 17a_zeromq_simple +.. automodule:: 17b_zeromq_multi_openfast +.. automodule:: 18_pitch_offsets +.. automodule:: 19_update_discon_version +.. automodule:: 20_active_wake_control +.. automodule:: 21_optional_inputs +.. automodule:: 22_cable_control +.. automodule:: 23_structural_control +.. automodule:: 24_floating_feedback +.. automodule:: 25_rotor_position_control +.. automodule:: 26_marine_hydro +.. automodule:: 27_power_ref_control +.. automodule:: 28_tower_resonance