Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/Publish_ROSCO.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ jobs:
if: false == contains( matrix.os, 'windows')
uses: fortran-lang/setup-fortran@v1

# macOS runners already ship Apple clang; installing llvm via Homebrew
# builds from source on untiered images (e.g. macos-14) and breaks.
- name: Install compiler
if: false == contains( matrix.os, 'windows')
if: contains( matrix.os, 'ubuntu')
id: install_cc
uses: rlalik/setup-cpp-compiler@master
with:
Expand Down Expand Up @@ -68,16 +70,16 @@ jobs:
if: contains( matrix.os, 'macos-14')
uses: pypa/[email protected]
env:
CC: ${{ steps.install_cc.outputs.cc }}
CXX: ${{ steps.install_cc.outputs.cxx }}
CC: clang
CXX: clang++
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="14.0"

- name: Build wheels mac-15
if: contains( matrix.os, 'macos-15')
uses: pypa/[email protected]
env:
CC: ${{ steps.install_cc.outputs.cc }}
CXX: ${{ steps.install_cc.outputs.cxx }}
CC: clang
CXX: clang++
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET="15.0"

- name: Build wheels windows
Expand Down
3 changes: 1 addition & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ build:
jobs:
# Read the docs needs a couple packages not in the environment file
pre_install:
- conda install -y cmake compilers sphinx sphinxcontrib-bibtex sphinx-jsonschema
- conda install sphinx_rtd_theme>=1.3
- mamba install -y compilers sphinx sphinxcontrib-bibtex sphinx-jsonschema
- pip install json-schema-for-humans
pre_build:
- pwd
Expand Down
2 changes: 1 addition & 1 deletion Examples/26_marine_hydro.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
26_marine_hydro
---------------
Run MHK turbine in OpenFAST with ROSCO torque controller
Run MHK turbine in OpenFAST with ROSCO pitch and torque controller
"""

import os
Expand Down
222 changes: 118 additions & 104 deletions Examples/31_fixed_pitch_mhk.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,42 @@
This example demonstrates the fixed-pitch control of a marine hydrodkinetic (MHK) turbine.

There are several ways to control the power output of a turbine in above-rated conditions.
In this example we demonstrate the following control configurations:
The first three configurations demonstrated here are the case studies verified against
simulation in :ref:`marine_hydro`:

#. Constant power underspeed (should be the default)
#. Constant power overspeed
#. Linear increasing power
#. Linear increasing power, leveling out
#. Generic numeric function
#. Constant power overspeed, nonlinear lookup table control
#. Example 1: Constant power overspeed, nonlinear control law
#. Example 2: Linear increasing power, underspeed with torque-lookup reference
#. Example 3: Generic user-defined power curve, underspeed with WSE-lookup reference

The remaining three are experimental. They exercise the rest of the fixed-pitch input
space and are not part of that verification campaign:

* Constant power underspeed
* Constant power overspeed, via WSE-lookup reference tracking
* Linear increasing power, leveling out below cut-out

More details about the controller methods can be found in :ref:`marine_hydro`.

The desired power curves of each configuration are as follows:

.. image:: ../images/examples/31_fixed_pitch_mhk_sched.png

In the first case, the reference generator speed is decreased (underspeed) to maintain a constant rated power above rated.
To slow down the generator, a higher torque must be used:
In the first case, the reference generator speed is increased (overspeed) to maintain a constant
rated power above rated, following the fixed control law tau = min(P_rated/omega, K*omega^2):

.. image:: ../images/examples/31_fixed_pitch_mhk_sim.png

All configurations are started softly: an open loop input ramps the torque rating
(``R_Torque``) from ``SOFT_START_R0`` to 1.0 with a sigma function over ``SOFT_START_DUR``
seconds, de-rating the generator torque limit while the turbine spins up.



'''

# Copying images, from docs/:
# cp ../Examples/examples_out/30_fixed_pitch_mhk_sched.png images/
# cp ../Examples/examples_out/30_fixed_pitch_mhk_sim.png images/
# cp ../Examples/examples_out/31_fixed_pitch_mhk_sched.png images/examples/
# cp ../Examples/examples_out/31_fixed_pitch_mhk_sim.png images/examples/

import os
from rosco.toolbox.ofTools.case_gen.run_FAST import run_FAST_ROSCO
Expand All @@ -53,10 +62,13 @@
example_out_dir = os.path.join(this_dir, 'examples_out')
os.makedirs(example_out_dir,exist_ok=True)

# Soft start up: R_Torque is ramped from SOFT_START_R0 to 1.0 over SOFT_START_DUR seconds
SOFT_START_R0 = 0.5
SOFT_START_DUR = 100.

def main():

FULL_TEST = False # Run a full test locally (True) or a shorter one for CI
sim_config = 1 # Choose which simulation configuration (1-6)

# Input yaml and output directory
parameter_filename = os.path.join(this_dir, 'Tune_Cases/RM1_MHK_FBP.yaml')
Expand All @@ -80,78 +92,74 @@ def main():
)


### Control configurations: overrides applied to the tuning yaml controller_params
# The first three are the verified case studies of :ref:`marine_hydro`; the rest
# demonstrate the rest of the FBP input space and are not part of that campaign.
# Each config states VS_FBP and its paired VS_ControlMode explicitly, rather than
# inheriting either from the tuning yaml.
control_configs = {
'Example 1: Constant Power Overspeed': { # marine_hydro Example 1
'VS_FBP': 1, # Constant power overspeed, nonlinear control law
'VS_ControlMode': 1, # Region 2 mode paired with VS_FBP = 1
'VS_FBP_speed_mode': 1,
'VS_FBP_U': [2.0, 4.0],
'VS_FBP_P': [1.0, 1.0],
},
'Example 2: Linear Increasing Power': { # marine_hydro Example 2
'VS_FBP': 3, # Torque-lookup reference
'VS_ControlMode': 4, # Region 2 mode paired with VS_FBP = 3
'VS_FBP_speed_mode': 0,
'VS_FBP_U': [2.0, 4.0],
'VS_FBP_P': [1.0, 2.0],
},
'Example 3: Generic User-Defined': { # marine_hydro Example 3
'VS_FBP': 2, # WSE-lookup reference
'VS_ControlMode': 2, # Region 2 mode paired with VS_FBP = 2
'VS_FBP_speed_mode': 0,
'VS_FBP_U': [2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0],
'VS_FBP_P': [1.0, 1.3, 1.6, 1.8, 1.9, 2.0, 1.9, 1.8, 1.7, 1.6, 1.5],
},
'Experimental: Constant Power Underspeed': { # Example 2 held at rated power
'VS_FBP': 3, # Torque-lookup reference
'VS_ControlMode': 4, # Region 2 mode paired with VS_FBP = 3
'VS_FBP_speed_mode': 0,
'VS_FBP_U': [2.0, 4.0],
'VS_FBP_P': [1.0, 1.0],
},
'Experimental: Constant Power Overspeed (WSE)': { # Example 1 schedule via reference tracking
'VS_FBP': 2, # WSE-lookup reference
'VS_ControlMode': 2, # Region 2 mode paired with VS_FBP = 2
'VS_FBP_speed_mode': 1, # Overspeed: check against the cavitation limit, exceeded for the RM1
'VS_FBP_U': [2.0, 4.0],
'VS_FBP_P': [1.0, 1.0],
},
'Experimental: Increasing Leveled Power': { # Example 2 leveling out below cut-out
'VS_FBP': 3, # Torque-lookup reference
'VS_ControlMode': 4, # Region 2 mode paired with VS_FBP = 3
'VS_FBP_speed_mode': 0,
'VS_FBP_U': [2.0, 3.0],
'VS_FBP_P': [1.0, 2.0],
},
}

### Tune controller cases
# Constant power underspeed (should be the default)
controller_params_1 = controller_params.copy()
controller_params_1['VS_FBP'] = 3 # Power reference
controller_params_1['VS_FBP_speed_mode'] = 0
controller_params_1['VS_FBP_U'] = [2.0, 4.0]
controller_params_1['VS_FBP_P'] = [1.0, 1.0]
controller_1 = ROSCO_controller.Controller(controller_params_1)
controller_1.tune_controller(turbine)

# Constant power overspeed
controller_params_2 = controller_params.copy()
controller_params_2['VS_FBP'] = 2 # Switch to WSE reference
controller_params_2['VS_FBP_speed_mode'] = 1
controller_params_2['VS_FBP_U'] = [2.0, 4.0]
controller_params_2['VS_FBP_P'] = [1.0, 1.0]
controller_2 = ROSCO_controller.Controller(controller_params_2)
controller_2.tune_controller(turbine)

# Linear increasing power
controller_params_3 = controller_params.copy()
controller_params_3['VS_FBP_speed_mode'] = 0
controller_params_2['VS_FBP_U'] = [2.0, 4.0]
controller_params_3['VS_FBP_P'] = [1.0, 2.0]
controller_3 = ROSCO_controller.Controller(controller_params_3)
controller_3.tune_controller(turbine)

# Linear increasing power, leveling out
controller_params_4 = controller_params.copy()
controller_params_4['VS_FBP_U'] = [2.0, 3.0]
controller_params_4['VS_FBP_P'] = [1.0, 2.0]
controller_4 = ROSCO_controller.Controller(controller_params_4)
controller_4.tune_controller(turbine)

# Generic numeric function
controller_params_5 = controller_params.copy()
controller_params_5['VS_FBP'] = 2 # WSE reference
controller_params_5['VS_FBP_U'] = [2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0]
controller_params_5['VS_FBP_P'] = [1.0, 1.3, 1.6, 1.8, 1.9, 2.0, 1.9, 1.8, 1.7, 1.6, 1.5]
controller_5 = ROSCO_controller.Controller(controller_params_5)
controller_5.tune_controller(turbine)

# Constant power overspeed, nonlinear lookup table control
controller_params_6 = controller_params.copy()
controller_params_6['VS_FBP'] = 1 # Constant power overspeed
controller_params_6['VS_FBP_speed_mode'] = 1
controller_params_6['VS_FBP_U'] = [2.0, 4.0]
controller_params_6['VS_FBP_P'] = [1.0, 1.0]
controller_params_6['VS_ControlMode'] = 1
controller_6 = ROSCO_controller.Controller(controller_params_6)
controller_6.tune_controller(turbine)


plot_labels = ['Constant Power Underspeed', 'Constant Power Overspeed', 'Linear Increasing Power', 'Increasing Leveled Power', 'Generic User-Defined']
controllers = []
for overrides in control_configs.values():
controller = ROSCO_controller.Controller({**controller_params, **overrides})
controller.tune_controller(turbine)
controllers.append(controller)

# The WSE overspeed config tracks the same schedule as Example 1, so dash it to keep both visible
line_styles = {'Experimental: Constant Power Overspeed (WSE)': '--'}

fig, axs = plt.subplots(3,1)
axs[0].plot(controller_1.v, controller_1.power_op, label=plot_labels[0])
axs[0].plot(controller_2.v, controller_2.power_op, label=plot_labels[1], linestyle='--')
axs[0].plot(controller_3.v, controller_3.power_op, label=plot_labels[2])
axs[0].plot(controller_4.v, controller_4.power_op, label=plot_labels[3])
axs[0].plot(controller_5.v, controller_5.power_op, label=plot_labels[4])
for label, cont in zip(control_configs, controllers):
line_style = line_styles.get(label, '-')
axs[0].plot(cont.v, cont.power_op, label=label, linestyle=line_style)
axs[1].plot(cont.v, cont.omega_gen_op, label=label, linestyle=line_style)
axs[2].plot(cont.v, cont.tau_op, label=label, linestyle=line_style)
axs[0].set_ylabel('Gen Power [W]')
axs[1].plot(controller_1.v, controller_1.omega_gen_op, label=plot_labels[0])
axs[1].plot(controller_2.v, controller_2.omega_gen_op, label=plot_labels[1], linestyle='--')
axs[1].plot(controller_3.v, controller_3.omega_gen_op, label=plot_labels[2])
axs[1].plot(controller_4.v, controller_4.omega_gen_op, label=plot_labels[3])
axs[1].plot(controller_5.v, controller_5.omega_gen_op, label=plot_labels[4])
axs[1].set_ylabel('Gen Speed [rad/s]')
axs[2].plot(controller_1.v, controller_1.tau_op, label=plot_labels[0])
axs[2].plot(controller_2.v, controller_2.tau_op, label=plot_labels[1], linestyle='--')
axs[2].plot(controller_3.v, controller_3.tau_op, label=plot_labels[2])
axs[2].plot(controller_4.v, controller_4.tau_op, label=plot_labels[3])
axs[2].plot(controller_5.v, controller_5.tau_op, label=plot_labels[4])
axs[2].set_ylabel('Gen Torque [N m]')
axs[2].set_xlabel('Flow Speed [m/s]')
axs[0].legend(loc='upper left', bbox_to_anchor=(.2, 2.35))
Expand All @@ -163,60 +171,66 @@ def main():
if False:
plt.show()
else:
fig_fname = os.path.join(example_out_dir, '30_fixed_pitch_mhk_sched.png')
fig_fname = os.path.join(example_out_dir, '31_fixed_pitch_mhk_sched.png')
print('Saving figure ' + fig_fname)
plt.savefig(fig_fname,bbox_inches='tight',)

# Write parameter input file for constant power underspeed controller
run_dir = os.path.join(example_out_dir, f'31_MHK/{sim_config}_config')
# Simulate all control configurations, in parallel
run_dir = os.path.join(example_out_dir, '31_MHK')
os.makedirs(run_dir,exist_ok=True)

# simulation set up
if FULL_TEST:
TMax = 60
TMax = SOFT_START_DUR + 100
else:
TMax = 5

all_controller_params = [
controller_params_1,
controller_params_2,
controller_params_3,
controller_params_4,
controller_params_5,
controller_params_6,
]

if sim_config in range(1,len(all_controller_params)+1):
controller_params = all_controller_params[sim_config-1]
else:
raise Exception(f'Invalid sim_config of {sim_config}. Note the 1-indexing.')
# Soft start up: phase in the torque rating with an open loop input
olc = ROSCO_controller.OpenLoopControl(t_max=SOFT_START_DUR)
olc.interp_series('R_torque', [0, SOFT_START_DUR], [SOFT_START_R0, 1.0], 'sigma')
ol_dict = olc.write_input(os.path.join(run_dir, '31_OL_Input.dat'))

r = run_FAST_ROSCO()
r.tuning_yaml = parameter_filename
r.controller_params = {
'PRC_Mode': 2, # power reference control
'OL_Mode': 1,
'open_loop': ol_dict,
'DISCON': {
'PRC_Comm': 1, # open loop inputs
},
}
r.wind_case_fcn = cl.power_curve
r.wind_case_opts = {
'U': [3.0],
'TMax': TMax,
}
r.case_inputs = {}
r.controller_params = controller_params
r.control_sweep_fcn = cl.sweep_yaml_input
r.control_sweep_opts = {'param_sweeps': list(control_configs.values())}
r.save_dir = run_dir
r.rosco_dir = rosco_dir
r.n_cores = min(len(control_configs), os.cpu_count())

r.run_FAST()

op = output_processing.output_processing()
fast_out = op.load_fast_out([os.path.join(run_dir,'RM1_MHK_FBP_0.out')], tmin=0)
out_files = [os.path.join(run_dir,f'RM1_MHK_FBP_{i}.out') for i in range(len(control_configs))]
fast_out = op.load_fast_out(out_files, tmin=0)
assert len(fast_out) == len(control_configs)

fig, axs = plt.subplots(4,1)
axs[0].plot(fast_out[0]['Time'], fast_out[0]['Wind1VelX'], label='Constant Power Underspeed')
for label, fo in zip(control_configs, fast_out):
axs[0].plot(fo['Time'], fo['Wind1VelX'], label=label)
axs[1].plot(fo['Time'], fo['GenSpeed'] * 2*np.pi/60, label=label)
axs[2].plot(fo['Time'], fo['GenTq'] * 1e3, label=label)
axs[3].plot(fo['Time'], fo['GenPwr'] * 1e3, label=label)
axs[0].set_ylabel('Flow Speed [m/s]',rotation=0, labelpad=50)
axs[1].plot(fast_out[0]['Time'], fast_out[0]['GenSpeed'] * 2*np.pi/60, label='Constant Power Underspeed')
axs[1].set_ylabel('Gen Speed [rad/s]',rotation=0, labelpad=50)
axs[2].plot(fast_out[0]['Time'], fast_out[0]['GenTq'] * 1e3, label='Constant Power Underspeed')
axs[2].set_ylabel('Gen Torque [N m]',rotation=0, labelpad=50)
axs[3].plot(fast_out[0]['Time'], fast_out[0]['GenPwr'] * 1e3, label='Constant Power Underspeed')
axs[3].set_ylabel('Gen Power [W]',rotation=0, labelpad=50)
axs[3].set_xlabel('Time [s]')
axs[0].legend(loc='upper left', bbox_to_anchor=(.2, 1.9))

plt.subplots_adjust(hspace=0.5)
fig.align_ylabels()
Expand All @@ -227,7 +241,7 @@ def main():
if False:
plt.show()
else:
fig_fname = os.path.join(example_out_dir, '30_fixed_pitch_mhk_sim.png')
fig_fname = os.path.join(example_out_dir, '31_fixed_pitch_mhk_sim.png')
print('Saving figure ' + fig_fname)
plt.savefig(fig_fname, bbox_inches='tight')

Expand Down
6 changes: 3 additions & 3 deletions Examples/Test_Cases/BAR_10/BAR_10_DISCON.IN
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
! Controller parameter input file for the BAR_10 wind turbine
! - File written using ROSCO version 2.10.6 controller tuning logic on 06/09/26
! - File written using ROSCO version 2.10.6 controller tuning logic on 09/16/26

!------- SIMULATION CONTROL ------------------------------------------------------------
1 ! LoggingLevel - 0: write no debug files, 1: write standard output .dbg-file, 2: LoggingLevel 1 + ROSCO LocalVars (.dbg2) 3: LoggingLevel 2 + complete avrSWAP-array (.dbg3)
Expand All @@ -10,7 +10,7 @@
!------- CONTROLLER FLAGS -------------------------------------------------
2 ! F_LPFType - 1: first-order low-pass filter, 2: second-order low-pass filter, [rad/s] (currently filters generator speed and pitch control signals
0 ! IPC_ControlMode - Turn Individual Pitch Control (IPC) for fatigue load reductions (pitch contribution) (0: off, 1: 1P reductions, 2: 1P+2P reductions)
2 ! VS_ControlMode - Generator torque control mode in above rated conditions (0- no torque control, 1- k*omega^2 with PI transitions, 2- WSE TSR Tracking, 3- Power-based TSR Tracking, 4- Torque-based TSR Tracking)
2 ! VS_ControlMode - Generator torque control mode in below rated conditions (0- no torque control, 1- k*omega^2 with PI transitions, 2- WSE TSR Tracking, 3- Power-based TSR Tracking, 4- Torque-based TSR Tracking)
0 ! VS_ConstPower - Do constant power torque control, where above rated torque varies, 0 for constant torque)
0 ! VS_FBP - Fixed blade pitch configuration mode (0- variable pitch (disabled), 1- constant power overspeed, 2- WSE-lookup reference tracking, 3- torque-lookup reference tracking)
1 ! PC_ControlMode - Blade pitch control mode (0: No pitch, fix to fine pitch, 1: active PI blade pitch control)
Expand Down Expand Up @@ -114,7 +114,7 @@
2 ! PRC_n - Number of elements in PRC_WindSpeeds and PRC_GenSpeeds array
0.07854 ! PRC_LPF_Freq - Frequency of the low pass filter on the wind speed estimate used to set PRC_GenSpeeds [rad/s]
3.0000 25.0000 ! PRC_WindSpeeds - Array of wind speeds used in rotor speed vs. wind speed lookup table [m/s]
7.9168e-01 7.9168e-01 ! PRC_GenSpeeds - Array of generator speeds corresponding to PRC_WindSpeeds [rad/s]
7.9853e+01 7.9853e+01 ! PRC_GenSpeeds - Array of generator speeds corresponding to PRC_WindSpeeds [rad/s]

!------- WIND SPEED ESTIMATOR ---------------------------------------------
102.996 ! WE_BladeRadius - Blade length (distance from hub center to blade tip), [m]
Expand Down
Loading
Loading