Skip to content

Commit

Permalink
Merge branch 'of350' into f/increase_max_unit
Browse files Browse the repository at this point in the history
  • Loading branch information
dzalkind committed Jul 10, 2023
2 parents d61f508 + 169d82a commit f5482dc
Show file tree
Hide file tree
Showing 16 changed files with 1,843 additions and 115 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/CI_rosco-pytools.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ jobs:
# Install OpenFAST
- name: Install OpenFAST
run: |
conda install openfast==3.4
conda install openfast==3.5
# Run examples
- name: Run examples
Expand Down Expand Up @@ -194,7 +194,7 @@ jobs:
# Install OpenFAST
- name: Install OpenFAST
run: |
conda install openfast==3.4
conda install openfast==3.5
# Run ROSCO Testing
- name: Run ROSCO testing
Expand Down
56 changes: 47 additions & 9 deletions Examples/23_structural_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@
import numpy as np
from ROSCO_toolbox.ofTools.fast_io.FAST_reader import InputReader_OpenFAST
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 (and hard-coded in ROSCO) a step change of -4e5 N on the first structural controller
is applied at 50 sec.
In the example below, we implement a smooth step change mimicing the exchange of ballast from the
upwind column to the down wind columns
The develop branch (as of Mar 3, 2023) of OpenFAST (v3.5.0, upcoming) is required to run this example
OpenFAST v3.5.0 is required to run this example
'''


Expand All @@ -45,7 +47,7 @@ def main():

# Input yaml and output directory
parameter_filename = os.path.join(rosco_dir,'Tune_Cases/IEA15MW_ballast.yaml')
run_dir = os.path.join(example_out_dir,'23_structural_control')
run_dir = os.path.join(example_out_dir,'23_structural_control_OL_2')
os.makedirs(run_dir,exist_ok=True)

# Read initial input file
Expand All @@ -65,6 +67,41 @@ def main():
for StC_file in reader.fst_vt['ServoDyn']['SStCfiles']:
reader.fst_vt['SStC'].append(reader.read_StC(StC_file))

# Set up open loop inputs to ROSCO
t_trans = 60
t_sigma = 80
t_max = 200

applied_force = [-2e6, 1e6, 1e6]

olc = OpenLoopControl(t_max=t_max)
olc.interp_timeseries(
'struct_control_1',
[0,t_trans,t_trans+t_sigma],
[0,0,applied_force[0]] ,
'sigma'
)

olc.interp_timeseries(
'struct_control_2',
[0,t_trans,t_trans+t_sigma],
[0,0,applied_force[1]] ,
'sigma'
)

olc.interp_timeseries(
'struct_control_3',
[0,t_trans,t_trans+t_sigma],
[0,0,applied_force[2]] ,
'sigma'
)


ol_params = olc.write_input(os.path.join(run_dir,'open_loop_ballast.dat'))

controller_params = {}
controller_params['open_loop'] = ol_params
controller_params['StC_Mode'] = 2

# simulation set up
r = run_FAST_ROSCO()
Expand All @@ -73,16 +110,17 @@ def main():
r.wind_case_fcn = cl.power_curve
r.wind_case_opts = {
'U': [9],
'T_max': 100,
'TMax': t_max,
}
r.case_inputs = {}
r.fst_vt = reader.fst_vt
r.save_dir = run_dir
r.rosco_dir = rosco_dir
r.fst_vt = reader.fst_vt
r.save_dir = run_dir
r.rosco_dir = rosco_dir
r.controller_params = controller_params
r.openfast_exe = '/Users/dzalkind/opt/anaconda3/envs/rosco-env/bin/openfast'

r.run_FAST()



if __name__=="__main__":
main()
34 changes: 31 additions & 3 deletions ROSCO/src/Controllers.f90
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ SUBROUTINE CableControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar)

! Internal Variables
Integer(IntKi) :: I_GROUP
CHARACTER(*), PARAMETER :: RoutineName = 'StructuralControl'



IF (CntrPar%CC_Mode == 1) THEN
Expand Down Expand Up @@ -751,31 +753,40 @@ SUBROUTINE CableControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar)

END DO

! Add RoutineName to error message
IF (ErrVar%aviFAIL < 0) THEN
ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg)
ENDIF

END SUBROUTINE CableControl

!-------------------------------------------------------------------------------------------------------------------------------
SUBROUTINE StructuralControl(avrSWAP, CntrPar, LocalVar, objInst)
SUBROUTINE StructuralControl(avrSWAP, CntrPar, LocalVar, objInst, ErrVar)
! Cable controller
! StC_Mode = 0, No cable control, this code not executed
! StC_Mode = 1, User-defined cable control
! StC_Mode = 2, Ballast-like control, not yet implemented
!
! Note that LocalVar%StC_Input() has a fixed max size of 12, which can be increased in rosco_types.yaml
!
USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances
USE ROSCO_Types, ONLY : ControlParameters, LocalVariables, ObjectInstances, ErrorVariables

REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller.

TYPE(ControlParameters), INTENT(INOUT) :: CntrPar
TYPE(LocalVariables), INTENT(INOUT) :: LocalVar
TYPE(ObjectInstances), INTENT(INOUT) :: objInst
TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar


! Internal Variables
Integer(IntKi) :: I_GROUP
CHARACTER(*), PARAMETER :: RoutineName = 'StructuralControl'



IF (CntrPar%StC_Mode == 1) THEN
! User defined control
! User defined control, step example

IF (LocalVar%Time > 500) THEN
! Step change in input of -4500 N
Expand All @@ -786,6 +797,18 @@ SUBROUTINE StructuralControl(avrSWAP, CntrPar, LocalVar, objInst)
END IF


ELSEIF (CntrPar%StC_Mode == 2) THEN


DO I_GROUP = 1,CntrPar%StC_Group_N
IF (CntrPar%Ind_StructControl(I_GROUP) > 0) THEN
LocalVar%StC_Input(I_GROUP) = interp1d(CntrPar%OL_Breakpoints, &
CntrPar%OL_StructControl(I_GROUP,:), &
LocalVar%Time,ErrVar)
ENDIF
ENDDO



END IF

Expand All @@ -795,6 +818,11 @@ SUBROUTINE StructuralControl(avrSWAP, CntrPar, LocalVar, objInst)
avrSWAP(CntrPar%StC_GroupIndex(I_GROUP)) = LocalVar%StC_Input(I_GROUP)
END DO

! Add RoutineName to error message
IF (ErrVar%aviFAIL < 0) THEN
ErrVar%ErrMsg = RoutineName//':'//TRIM(ErrVar%ErrMsg)
ENDIF

END SUBROUTINE StructuralControl
!-------------------------------------------------------------------------------------------------------------------------------
END MODULE Controllers
2 changes: 1 addition & 1 deletion ROSCO/src/DISCON.F90
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ SUBROUTINE DISCON(avrSWAP, aviFAIL, accINFILE, avcOUTNAME, avcMSG) BIND (C, NAME

! Structural control
IF (CntrPar%StC_Mode > 0) THEN
CALL StructuralControl(avrSWAP,CntrPar,LocalVar, objInst)
CALL StructuralControl(avrSWAP,CntrPar,LocalVar, objInst, ErrVar)
END IF

IF ( CntrPar%LoggingLevel > 0 ) THEN
Expand Down
6 changes: 0 additions & 6 deletions ROSCO/src/ReadSetParameters.f90
Original file line number Diff line number Diff line change
Expand Up @@ -738,12 +738,6 @@ SUBROUTINE CheckInputs(LocalVar, CntrPar, avrSWAP, ErrVar, size_avcMSG)
ErrVar%ErrMsg = 'F_NotchType must be 0, 1, 2, or 3.'
ENDIF

! F_NotchType
IF ((CntrPar%F_NotchType < 0) .OR. (CntrPar%F_NotchType > 2)) THEN
ErrVar%aviFAIL = -1
ErrVar%ErrMsg = 'F_NotchType must be 0, 1, or 2.'
ENDIF

! IPC_ControlMode
IF ((CntrPar%IPC_ControlMode < 0) .OR. (CntrPar%IPC_ControlMode > 2)) THEN
ErrVar%aviFAIL = -1
Expand Down
94 changes: 91 additions & 3 deletions ROSCO_toolbox/ofTools/fast_io/FAST_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,30 @@ def read_ElastoDyn(self, ed_file):
self.set_outlist(self.fst_vt['outlist']['ElastoDyn'], channel_list)

data = f.readline()

# ElastoDyn optional outlist
try:
f.readline()
self.fst_vt['ElastoDyn']['BldNd_BladesOut'] = int(f.readline().split()[0])
self.fst_vt['ElastoDyn']['BldNd_BlOutNd'] = f.readline().split()[0]

f.readline()
data = f.readline()
while data.split()[0] != 'END':
if data.find('"')>=0:
channels = data.split('"')
opt_channel_list = channels[1].split(',')
else:
row_string = data.split(',')
if len(row_string)==1:
opt_channel_list = row_string[0].split('\n')[0]
else:
opt_channel_list = row_string
self.set_outlist(self.fst_vt['outlist']['ElastoDyn_Nodes'], opt_channel_list)
data = f.readline()
except:
# The optinal outlist does not exist.
None

f.close()

Expand Down Expand Up @@ -601,6 +625,30 @@ def read_BeamDyn(self, bd_file):
channel_list = channels[1].split(',')
self.set_outlist(self.fst_vt['outlist']['BeamDyn'], channel_list)
data = f.readline()

# BeamDyn optional outlist
try:
f.readline()
# self.fst_vt['BeamDyn']['BldNd_BladesOut'] = int(f.readline().split()[0])
self.fst_vt['BeamDyn']['BldNd_BlOutNd'] = f.readline().split()[0]

f.readline()
data = f.readline()
while data.split()[0] != 'END':
if data.find('"')>=0:
channels = data.split('"')
opt_channel_list = channels[1].split(',')
else:
row_string = data.split(',')
if len(row_string)==1:
opt_channel_list = row_string[0].split('\n')[0]
else:
opt_channel_list = row_string
self.set_outlist(self.fst_vt['outlist']['BeamDyn_Nodes'], opt_channel_list)
data = f.readline()
except:
# The optinal outlist does not exist.
None

f.close()

Expand Down Expand Up @@ -661,6 +709,7 @@ def read_InflowWind(self):
self.fst_vt['InflowWind']['WindType'] = int(f.readline().split()[0])
self.fst_vt['InflowWind']['PropagationDir'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['VFlowAng'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['VelInterpCubic'] = bool_read(f.readline().split()[0])
self.fst_vt['InflowWind']['NWindVel'] = int(f.readline().split()[0])
self.fst_vt['InflowWind']['WindVxiList'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['WindVyiList'] = float_read(f.readline().split()[0])
Expand Down Expand Up @@ -717,6 +766,21 @@ def read_InflowWind(self):
self.fst_vt['InflowWind']['Z0'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['XOffset'] = float_read(f.readline().split()[0])

# LIDAR parameters
f.readline()
self.fst_vt['InflowWind']['SensorType'] = int(f.readline().split()[0])
self.fst_vt['InflowWind']['NumPulseGate'] = int(f.readline().split()[0])
self.fst_vt['InflowWind']['PulseSpacing'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['NumBeam'] = int(f.readline().split()[0])
self.fst_vt['InflowWind']['FocalDistanceX'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['FocalDistanceY'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['FocalDistanceZ'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['RotorApexOffsetPos'] = [idx.strip() for idx in f.readline().split('RotorApexOffsetPos')[0].split(',')]
self.fst_vt['InflowWind']['URefLid'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['MeasurementInterval'] = float_read(f.readline().split()[0])
self.fst_vt['InflowWind']['LidRadialVel'] = bool_read(f.readline().split()[0])
self.fst_vt['InflowWind']['ConsiderHubMotion'] = int(f.readline().split()[0])

# Inflow Wind Output Parameters (inflow_out_params)
f.readline()
self.fst_vt['InflowWind']['SumPrint'] = bool_read(f.readline().split()[0])
Expand Down Expand Up @@ -889,6 +953,30 @@ def read_AeroDyn15(self):
self.set_outlist(self.fst_vt['outlist']['AeroDyn'], channel_list)
data = f.readline()

# AeroDyn15 optional outlist
try:
f.readline()
self.fst_vt['AeroDyn15']['BldNd_BladesOut'] = int(f.readline().split()[0])
self.fst_vt['AeroDyn15']['BldNd_BlOutNd'] = f.readline().split()[0]

f.readline()
data = f.readline()
while data.split()[0] != 'END':
if data.find('"')>=0:
channels = data.split('"')
opt_channel_list = channels[1].split(',')
else:
row_string = data.split(',')
if len(row_string)==1:
opt_channel_list = row_string[0].split('\n')[0]
else:
opt_channel_list = row_string
self.set_outlist(self.fst_vt['outlist']['AeroDyn_Nodes'], opt_channel_list)
data = f.readline()
except:
# The optinal outlist does not exist.
None

f.close()

self.read_AeroDyn15Blade()
Expand Down Expand Up @@ -1082,11 +1170,11 @@ def read_AeroDyn15OLAF(self, olaf_filename):
f.readline()
self.fst_vt['AeroDyn15']['OLAF']['VelocityMethod'] = int_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['TreeBranchFactor']= float_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['PartPerSegment'] = int(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['PartPerSegment'] = int_read(f.readline().split()[0])
f.readline()
f.readline()
self.fst_vt['AeroDyn15']['OLAF']['WrVTk'] = int(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['nVTKBlades'] = int(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['WrVTk'] = int_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['nVTKBlades'] = int_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['VTKCoord'] = int_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['VTK_fps'] = float_read(f.readline().split()[0])
self.fst_vt['AeroDyn15']['OLAF']['nGridOut'] = int_read(f.readline().split()[0])
Expand Down
Loading

0 comments on commit f5482dc

Please sign in to comment.