-
Notifications
You must be signed in to change notification settings - Fork 113
Description
Desired use case
At the moment i'm using the 14_open_loop_controller to generate any kind of yaw maneuver such that me and my team can study the effects of dynamic yawing on blade root and tower bending moments. However we encountered a weird bug, when using the olc.interp_series to apply any kind of yaw angle.
Description
When using the olc.interp_series for any kind of yaw angle maneuver, high yaw rates and accelerations are generated in the beginning of our simulations. This highly effects our computation time, because we need to wait a long time before the transients in the tower bending moment are gone, due to the bad damping of the tower.
My question for you guys is the following: Is there a way to prevent these high oscillations at the start of the simulation (thus prevent these high yaw rates and accelerations)?
Current behavior
Even though the yaw angle follows the given input nicely, the yaw rate (which communicates with OpenFAST) and yaw acceleration are not as expected and highly influence the applied loads and computation time.
Graphs of current behavior
Expected behavior
We would like to know where these high yaw rates and accelerations come from and how to mitigate this such that less big transients are generated and thus the computation time can be lowered.
The used code (edited 14_open_loop_control case)
Until now only static yaw angles are tested, this was done by slowly maneuvering to the desired yaw angle in the first seconds, staying in idle in the following seconds (to let transients die out) and then the real maneuver is applied.
`
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
Parameters
dt = 0.01
t_ramp = 200.0
t_wait = 500.0
t_maneuver = 600.0
begin_maneuver = t_ramp + t_wait
Windspeed = 10
Simulation_time = t_ramp + t_wait + t_maneuver
final_yaw_angle_degree = 20
Python Modules
import os
import matplotlib.pyplot as plt
import numpy as np
from openfast_toolbox.io import FASTOutputFile
from openfast_toolbox.postpro import equivalent_load
import pandas as pd
ROSCO toolbox modules
from rosco import discon_lib_path
from rosco.toolbox import controller as ROSCO_controller
from rosco.toolbox import turbine as ROSCO_turbine
from rosco.toolbox import utilities as ROSCO_utilities
from rosco.toolbox.ofTools.fast_io import output_processing
from rosco.toolbox.inputs.validation import load_rosco_yaml
from rosco.toolbox.ofTools.case_gen.CaseLibrary import set_channels
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
def main():
this_dir = os.path.dirname(os.path.abspath(file))
tune_dir = '/data/leuven/366/vsc36692/miniconda3/envs/science/ROSCO/Examples/Test_Cases/IEA-15-240-RWT/IEA-15-240-RWT-Monopile'
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, 'IEA-15-240-RWT-Monopile_ROSCO.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=Simulation_time)
=== YAW SIGNALS: ramp (0–200s) → wait (200–700s) → arbitrary maneuver (700–1300s) ===
----- Define PHASE 3 desired yaw signal (edit this for other maneuvers) -----
Example 1: static yaw at 'final_yaw_angle_degree'
maneuver_angle_rad = np.deg2rad(final_yaw_angle_degree)
def yaw_maneuver_func(t_rel):
# t_rel in [0, t_maneuver]
return maneuver_angle_rad
# If later want something else --> replace yaw_maneuver_func
# Initial yaw to reach at end of ramp is the first value of phase-3 signal
yaw_target_init = yaw_maneuver_func(0.0)
# ----- Phase 1: triangular angle ramp (0–200s) to yaw_target_init -----
t1_up = np.linspace(0, t_ramp/2, int(t_ramp/(2*dt)), endpoint=False)
t1_dn = np.linspace(t_ramp/2, t_ramp, int(t_ramp/(2*dt)))
# quadratic-in/quadratic-out angle profile (integral of triangular velocity)
yaw_up = (yaw_target_init/2) * (t1_up/(t_ramp/2))**2
yaw_dn = yaw_target_init * (1 - 0.5*((t_ramp - t1_dn)/(t_ramp/2))**2)
time_phase1 = np.concatenate([t1_up, t1_dn])
yaw_phase1 = np.concatenate([yaw_up, yaw_dn])
# ----- Phase 2: wait (200–700s) -----
time_phase2 = np.linspace(time_phase1[-1] + dt,
time_phase1[-1] + t_wait,
int(t_wait/dt))
yaw_phase2 = np.full_like(time_phase2, yaw_target_init)
# ----- Phase 3: arbitrary maneuver (700–1300s) -----
time_phase3 = np.linspace(time_phase2[-1] + dt,
time_phase2[-1] + t_maneuver,
int(t_maneuver/dt))
yaw_phase3 = np.array([yaw_maneuver_func(t_rel) for t_rel in (time_phase3 - time_phase3[0])])
# ----- Combine & apply -----
yaw_times = np.concatenate([time_phase1, time_phase2, time_phase3])
yaw_values = np.concatenate([yaw_phase1, yaw_phase2, yaw_phase3])
olc.interp_timeseries('nacelle_yaw', yaw_times, yaw_values, 'sigma')
# Plot open loop timeseries
fig,ax = olc.plot_timeseries()
if False:
plt.show()
else:
fig.savefig(os.path.join(example_out_dir,f'14_OL_Sim/Plots/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}
case_inputs[('ServoDyn','Ptch_Cntrl')] = {'vals': [1], 'group': 0}
case_inputs[('ServoDyn','YCMode')] = {'vals': [5], 'group': 0}
case_inputs[('ServoDyn','TYCOn')] = {'vals': [0], 'group': 0}
case_inputs[('ElastoDyn','YawDOF')] = {'vals': ['True'], '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': [Simulation_time], 'group': 0}
case_inputs[('InflowWind','HWindSpeed')] = {'vals': [Windspeed], '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()
......
`