Skip to content

Commit

Permalink
Merge working example of ZMQ from abhineet
Browse files Browse the repository at this point in the history
  • Loading branch information
abhineet-gupta committed Nov 6, 2023
2 parents 5583fdf + ef9e7d2 commit 4682769
Show file tree
Hide file tree
Showing 26 changed files with 495 additions and 139 deletions.
1 change: 1 addition & 0 deletions Examples/11_robust_tuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def run_example():

# Path options
example_out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'examples_out')
os.makedirs(example_out_dir,exist_ok=True)
output_name = '11_robust_scheduling'
path_options = {'output_dir': example_out_dir,
'output_name': output_name
Expand Down
59 changes: 51 additions & 8 deletions Examples/17_zeromq_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,44 @@
from ROSCO_toolbox import sim as ROSCO_sim
from ROSCO_toolbox import turbine as ROSCO_turbine
from ROSCO_toolbox import controller as ROSCO_controller
from ROSCO_toolbox.ofTools.fast_io import output_processing
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)

def run_zmq():
connect_zmq = True
s = turbine_zmq_server(network_address="tcp://*:5555", timeout=10.0, verbose=True)
s = turbine_zmq_server(network_address="tcp://*:5555", timeout=10.0)
while connect_zmq:
# Get latest measurements from ROSCO
measurements = s.get_measurements()

# Decide new control input based on measurements

# Yaw set point
current_time = measurements['Time']
if current_time <= 10.0:
yaw_setpoint = 0.0
else:
yaw_setpoint = 20.0
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 commands back to ROSCO
pitch_command = 3 * [0]
pitch_command[0] = col_pitch_command
pitch_command[1] = col_pitch_command
pitch_command[2] = col_pitch_command

# Send new setpoints back to ROSCO
s.send_setpoints(nacelleHeading=yaw_setpoint)
# Send new setpoints back to ROSCO
s.send_setpoints(bladePitch=pitch_command, nacelleHeading=yaw_setpoint)

if measurements['iStatus'] == -1:
connect_zmq = False
Expand All @@ -56,6 +75,7 @@ def sim_rosco():
# Enable ZeroMQ & yaw control
controller_params['Y_ControlMode'] = 1
controller_params['ZMQ_Mode'] = 1
controller_params['ZMQ_UpdatePeriod'] = 0.025

# Specify controller dynamic library path and name
this_dir = os.path.dirname(os.path.abspath(__file__))
Expand All @@ -76,19 +96,22 @@ def sim_rosco():

# Load turbine data from OpenFAST and rotor performance text file
cp_filename = os.path.join(
tune_dir, path_params['FAST_directory'], path_params['rotor_performance_filename'])
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_params['LoggingLevel'] = 2
controller = ROSCO_controller.Controller(controller_params)
controller.tune_controller(turbine)

# Write parameter input file
param_filename = os.path.join(this_dir, 'DISCON_zmq.IN')
sim_dir = os.path.join(example_out_dir,'17_ZeroMQ')
os.makedirs(sim_dir,exist_ok=True)
param_filename = os.path.join(sim_dir, 'DISCON_zmq.IN')
write_DISCON(
turbine, controller,
param_file=param_filename,
Expand All @@ -97,7 +120,11 @@ def sim_rosco():


# Load controller library
controller_int = ROSCO_ci.ControllerInterface(lib_name, param_filename=param_filename, sim_name='sim-zmq')
controller_int = ROSCO_ci.ControllerInterface(
lib_name,
param_filename=param_filename,
sim_name=os.path.join(sim_dir,'sim-zmq')
)

# Load the simulator
sim = ROSCO_sim.Sim(turbine, controller_int)
Expand All @@ -121,8 +148,24 @@ def sim_rosco():
if False:
plt.show()
else:
plt.savefig(os.path.join(example_out_dir, '16_NREL5MW_zmqYaw.png'))
plt.savefig(os.path.join(example_out_dir, '17_NREL5MW_ZMQ.png'))

# Check that info is passed to ROSCO
op = output_processing.output_processing()
local_vars = op.load_fast_out([os.path.join(sim_dir,'sim-zmq.RO.dbg2')], tmin=0)
fig, axs = plt.subplots(2,1)
axs[0].plot(local_vars[0]['Time'],local_vars[0]['ZMQ_YawOffset'])
axs[1].plot(local_vars[0]['Time'],local_vars[0]['ZMQ_PitOffset'])

if False:
plt.show()
else:
plt.savefig(os.path.join(example_out_dir, '17_NREL5MW_ZMQ_Setpoints.png'))

# Spot check input at time = 30 sec.
ind_30 = local_vars[0]['Time'] == TIME_CHECK
np.testing.assert_almost_equal(local_vars[0]['ZMQ_YawOffset'][ind_30], DESIRED_YAW_OFFSET)
np.testing.assert_almost_equal(local_vars[0]['ZMQ_PitOffset'][ind_30], DESIRED_PITCH_OFFSET, decimal=3)

if __name__ == "__main__":
p1 = mp.Process(target=run_zmq)
Expand Down
2 changes: 1 addition & 1 deletion Examples/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def execute_script(fscript):
# Use runpy to execute examples
s = time()
runpy.run_path(os.path.realpath(fullpath), run_name='__main__')
print(time() - s, "seconds to run")
print(f"{fscript} took {time() - s} seconds to run")

class TestExamples(unittest.TestCase):

Expand Down
19 changes: 11 additions & 8 deletions ROSCO/rosco_registry/rosco_types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,16 @@ LocalVariables:
<<: *complex
description: Complex angle for each blade, sum of modes?
size: 3
ZMQ_YawOffset:
<<: *real
description: Yaw offset command, [rad]
ZMQ_TorqueOffset:
<<: *real
description: Torque offset command, [Nm]
ZMQ_PitOffset:
<<: *real
size: 3
description: Pitch command offset provided by ZeroMQ
WE:
<<: *derived_type
id: WE
Expand Down Expand Up @@ -1446,14 +1456,7 @@ ExtDLL_Type:
size: 3
length: 1024
description: The name of the procedure in the DLL that will be called.

ZMQ_Variables:
ZMQ_Flag:
<<: *logical
description: Flag if we're using zeroMQ at all (0-False, 1-True)
Yaw_Offset:
<<: *real
description: Yaw offsety command, [rad]


ExtControlType:
avrSWAP:
Expand Down
26 changes: 26 additions & 0 deletions ROSCO/rosco_registry/wfc_interface.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
measurements: [
iStatus,
Time,
VS_MechGenPwr,
VS_GenPwr,
GenSpeed,
RotSpeed,
GenTqMeas,
NacHeading,
NacVane,
HorWindV,
rootMOOP(1),
rootMOOP(2),
rootMOOP(3),
FA_Acc,
NacIMU_FA_Acc,
Azimuth,
]

setpoints: [
ZMQ_TorqueOffset,
ZMQ_YawOffset,
ZMQ_PitOffset(1),
ZMQ_PitOffset(2),
ZMQ_PitOffset(3),
]
11 changes: 5 additions & 6 deletions ROSCO/rosco_registry/write_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def write_roscoio(yfile):
file.write(" TYPE(ObjectInstances), INTENT(INOUT) :: objInst\n")
file.write(" TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar\n")
file.write(" INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME\n")
file.write(" CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName \n")
file.write(" CHARACTER(size_avcOUTNAME), INTENT(IN) :: RootName \n")
file.write(" \n")
file.write(" INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart)\n")
file.write(" INTEGER(IntKi) :: I ! Generic index.\n")
Expand Down Expand Up @@ -118,16 +118,15 @@ def write_roscoio(yfile):
# ------------------------------------------------
# ------------ ReadRestartFile ------------------
# ------------------------------------------------
file.write('SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, zmqVar, ErrVar)\n')
file.write('SUBROUTINE ReadRestartFile(avrSWAP, LocalVar, CntrPar, objInst, PerfData, RootName, size_avcOUTNAME, ErrVar)\n')
file.write(" TYPE(LocalVariables), INTENT(INOUT) :: LocalVar\n")
file.write(" TYPE(ControlParameters), INTENT(INOUT) :: CntrPar\n")
file.write(" TYPE(ObjectInstances), INTENT(INOUT) :: objInst\n")
file.write(" TYPE(PerformanceData), INTENT(INOUT) :: PerfData\n")
file.write(" TYPE(ErrorVariables), INTENT(INOUT) :: ErrVar\n")
file.write(" TYPE(ZMQ_Variables), INTENT(INOUT) :: zmqVar\n")
file.write(" REAL(ReKi), INTENT(IN) :: avrSWAP(*)\n")
file.write(" INTEGER(IntKi), INTENT(IN) :: size_avcOUTNAME\n")
file.write(" CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName \n")
file.write(" CHARACTER(size_avcOUTNAME), INTENT(IN) :: RootName \n")
file.write(" \n")
file.write(" INTEGER(IntKi) :: Un ! I/O unit for pack/unpack (checkpoint & restart)\n")
file.write(" INTEGER(IntKi) :: I ! Generic index.\n")
Expand Down Expand Up @@ -163,7 +162,7 @@ def write_roscoio(yfile):
file.write(' Close ( Un )\n')
file.write(' ENDIF\n')
file.write(' ! Read Parameter files\n')
file.write(' CALL ReadControlParameterFileSub(CntrPar, LocalVar, zmqVar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, RootName, ErrVar)\n')
file.write(' CALL ReadControlParameterFileSub(CntrPar, LocalVar, LocalVar%ACC_INFILE, LocalVar%ACC_INFILE_SIZE, RootName, ErrVar)\n')
file.write(' IF (CntrPar%WE_Mode > 0) THEN\n')
file.write(' CALL READCpFile(CntrPar, PerfData, ErrVar)\n')
file.write(' ENDIF\n')
Expand All @@ -188,7 +187,7 @@ def write_roscoio(yfile):
file.write(' INTEGER(IntKi), SAVE :: UnDb2 ! I/O unit for the debugging information, avrSWAP\n')
file.write(' INTEGER(IntKi), SAVE :: UnDb3 ! I/O unit for the debugging information, avrSWAP\n')
file.write(' REAL(ReKi), INTENT(INOUT) :: avrSWAP(*) ! The swap array, used to pass data to, and receive data from, the DLL controller.\n')
file.write(' CHARACTER(size_avcOUTNAME-1), INTENT(IN) :: RootName ! a Fortran version of the input C string (not considered an array here) [subtract 1 for the C null-character]\n')
file.write(' CHARACTER(size_avcOUTNAME), INTENT(IN) :: RootName ! a Fortran version of the input C string (not considered an array here) [subtract 1 for the C null-character]\n')
file.write(' CHARACTER(200) :: Version ! git version of ROSCO\n')
file.write(' CHARACTER(15), ALLOCATABLE :: DebugOutStrings(:), DebugOutUnits(:)\n')
file.write('\n')
Expand Down
Loading

0 comments on commit 4682769

Please sign in to comment.