diff --git a/WTC_toolbox/control_interface.py b/WTC_toolbox/control_interface.py index 7f4caf3a5..a729a0a3b 100644 --- a/WTC_toolbox/control_interface.py +++ b/WTC_toolbox/control_interface.py @@ -26,6 +26,11 @@ class ConInt(): def __init__(self, lib_name): """ Setup the interface + + Parameters: + ----------- + lib_name = str + name of dynamic library containing controller, (.dll,.so,.dylib) """ self.lib_name = lib_name self.param_name = 'DISCON.IN' # this appears to be hard-coded so match here for now @@ -40,12 +45,11 @@ def __init__(self, lib_name): # Initialize self.pitch = 0 self.torque = 0 - + # -- discon self.discon = cdll.LoadLibrary(self.lib_name) - self.avrSWAP = np.zeros(self.avr_size) - # Define avrSWAP some + # Define some avrSWAP parameters self.avrSWAP[2] = self.DT self.avrSWAP[60] = self.num_blade @@ -57,51 +61,56 @@ def __init__(self, lib_name): self.avrSWAP[49] = self.char_buffer self.avrSWAP[50] = self.char_buffer + # Initialize DISCON and related self.aviFAIL = c_int32() # 1 self.accINFILE = self.param_name.encode('utf-8') self.avcOUTNAME = create_string_buffer(1000) # 'DEMO'.encode('utf-8') self.avcMSG = create_string_buffer(1000) - - # self.discon.DISCON.argtypes = [ndpointer(c_double, flags="C_CONTIGUOUS"), POINTER(c_int32), c_char_p, c_char_p, c_char_p] # (all defined by ctypes) self.discon.DISCON.argtypes = [POINTER(c_float), POINTER(c_int32), c_char_p, c_char_p, c_char_p] # (all defined by ctypes) + # Run DISCON self.call_discon() - - # First call - # self.discon.DISCON(self.avrSWAP, byref(self.aviFAIL), self.accINFILE, self.avcOUTNAME, self.avcMSG) - - # Code as not first run self.avrSWAP[0] = 1 def call_discon(self): - - # Convert AVR swap to the right thing + ''' + Call DISCON.dll (or .so,.dylib) + ''' + # Convert AVR swap to the c pointer c_float_p = POINTER(c_float) data = self.avrSWAP.astype(np.float32) - data_p = data.ctypes.data_as(c_float_p) p_data = data.ctypes.data_as(c_float_p) # Run DISCON self.discon.DISCON(p_data, byref(self.aviFAIL), self.accINFILE, self.avcOUTNAME, self.avcMSG) # Push back to avr swap - # print(data_p[47]) - # print(self.avrSWAP[47]) - #for i in range(len(self.avrSWAP)): - # self.avrSWAP - #print('len',len(data_p),len(self.avrSWAP)) - - self.avrSWAP = data def call_controller(self,t,dt,pitch,genspeed,rotspeed,ws): + ''' + Runs the controller. Passes current turbine state to the controller, and returns control inputs back - - + Parameters: + ----------- + t: float + time, (s) + dt: float + timestep, (s) + pitch: float + blade pitch, (rad) + genspeed: float + generator speed, (rad/s) + rotspeed: float + rotor speed, (rad/s) + ws: float + wind speed, (m/s) + ''' + # Add states to avr self.avrSWAP[1] = t self.avrSWAP[2] = dt @@ -111,17 +120,12 @@ def call_controller(self,t,dt,pitch,genspeed,rotspeed,ws): self.avrSWAP[26] = ws self.call_discon() - # self.discon.DISCON(self.avrSWAP, byref(self.aviFAIL), self.accINFILE, self.avcOUTNAME, self.avcMSG) self.pitch = self.avrSWAP[41] - self.torque = self.avrSWAP[47] + self.torque = self.avrSWAP[46] return(self.torque,self.pitch) def show_control_values(self): print('Pitch',self.pitch) - print('Torque',self.torque) - - -#discon.DISCON.argtypes = [POINTER(c_float), c_int, c_char_p, c_char_p, c_char_p] # (all defined by ctypes) -# discon.DISCON(byref(avrSWAP), aviFAIL, accINFILE, avcOUTNAME, avcMSG) \ No newline at end of file + print('Torque',self.torque) \ No newline at end of file diff --git a/WTC_toolbox/sim.py b/WTC_toolbox/sim.py index be0eb0e15..2e6d58b43 100644 --- a/WTC_toolbox/sim.py +++ b/WTC_toolbox/sim.py @@ -37,14 +37,29 @@ def __init__(self, turbine, controller_int): self.gen_eff = 0.95 - def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0): - - # Store some for conveniente + def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0, make_plots=True): + ''' + Simulate simplified turbine model using a complied controller (.dll or similar). + - currently a 1DOF rotor model + + Parameters: + ----------- + t_array: float + Array of time steps, (s) + ws_array: float + Array of wind speeds, (s) + rotor_rpm_init: float, optional + initial rotor speed, (rpm) + init_pitch: float, optional + initial blade pitch angle, (deg) + make_plots: bool, optional + True: generate plots, False: don't. + ''' + # Store turbine data for conveniente dt = t_array[1] - t_array[0] R = self.turbine.RotorRad GBRatio = self.turbine.Ng - # Declare output arrays pitch = np.ones_like(t_array) * init_pitch rot_speed = np.ones_like(t_array) * rotor_rpm_init * rpm2RadSec # represent rot speed in rad / s @@ -53,7 +68,8 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0): gen_torque = np.ones_like(t_array) # * trq_cont(turbine_dict, gen_speed[0]) gen_power = np.ones_like(t_array) * 0.0 - + # Test for cc_blade Cq information. + # - If not, assume available matrices loaded from text file, and interpolate those try: self.turbine.cc_rotor.evaluate(ws_array[1], [rot_speed[0]/rpm2RadSec], [pitch[0]], @@ -67,8 +83,9 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0): for i, t in enumerate(t_array): if i == 0: continue # Skip the first run - ws = ws_array[i] + + # Load current Cq data if use_interpolated: tsr = rot_speed[i-1] * self.turbine.RotorRad / ws cq = self.turbine.Cq.interp_surface([pitch[i-1]],tsr) @@ -77,33 +94,10 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0): [rot_speed[i-1]/rpm2RadSec], [pitch[i-1]], coefficients=True) - # try: - # P, T, Q, M, Cp, Ct, cq, CM = self.turbine.rotor.evaluate([ws], - # [rot_speed[i-1]/rpm2RadSec], - # [pitch[i-1]], - # coefficients=True) - # except: - # print('CC BLADE PROBLEM') - # e = sys.exc_info()[0] - # print(e) - # # carry through the past values and continue - # pitch[i] = pitch[i-1] - # rot_speed[i] = rot_speed[i-1] - # gen_speed[i] = gen_speed[i-1] - # aero_torque[i] = aero_torque[i-1] - # gen_torque[i] = gen_torque[i-1] - # gen_power[i] = gen_power[i-1] - # continue + # Update the turbine state + # -- 1DOF model: rotor speed and generator speed (scaled by Ng) aero_torque[i] = 0.5 * self.turbine.rho * (np.pi * R**2) * cq * R * ws**2 - - # # Save these values for plotting - # cq_array[i] = cq - # cp_array[i] = Cp[0] - # ct_array[i] = Ct[0] - # tsr_array[i] = tsr - - # Update the rotor speed and generator speed rot_speed[i] = rot_speed[i-1] + (dt/self.turbine.J)*(aero_torque[i] * self.gb_eff - self.turbine.Ng * gen_torque[i-1]) gen_speed[i] = rot_speed[i] * self.turbine.Ng @@ -123,28 +117,23 @@ def sim_ws_series(self,t_array,ws_array,rotor_rpm_init=10,init_pitch=0.0): self.t_array = t_array self.ws_array = ws_array - - fig, axarr = plt.subplots(4,1,sharex=True,figsize=(6,10)) - - - - ax = axarr[0] - ax.plot(self.t_array,self.ws_array,label='Wind Speed') - ax.grid() - ax.legend() - - ax = axarr[1] - ax.plot(self.t_array,self.rot_speed,label='Rot Speed') - ax.grid() - ax.legend() - - ax = axarr[2] - ax.plot(self.t_array,self.gen_torque,label='Gen Torque') - ax.grid() - ax.legend() - - ax = axarr[3] - ax.plot(self.t_array,self.pitch,label='Bld Pitch') - ax.grid() - ax.legend() + if make_plots: + fig, axarr = plt.subplots(4,1,sharex=True,figsize=(6,10)) + + ax = axarr[0] + ax.plot(self.t_array,self.ws_array,label='Wind Speed') + ax.grid() + ax.legend() + ax = axarr[1] + ax.plot(self.t_array,self.rot_speed,label='Rot Speed') + ax.grid() + ax.legend() + ax = axarr[2] + ax.plot(self.t_array,self.gen_torque,label='Gen Torque') + ax.grid() + ax.legend() + ax = axarr[3] + ax.plot(self.t_array,self.pitch,label='Bld Pitch') + ax.grid() + ax.legend() \ No newline at end of file