import numpy as np import time import matplotlib.pyplot as plt import random # get gekko package with: # pip install gekko from gekko import GEKKO # get tclab package with: # pip install tclab from tclab import TCLab # save txt file def save_txt(t,Q1,Q2,T1,T2): data = np.vstack((t,Q1,Q2,T1,T2)) # vertical stack data = data.T # transpose data top = 'Time (sec), Heater 1, Heater 2, ' \ + 'Temperature 1, Temperature 2' np.savetxt('data.txt',data,delimiter=',',header=top,comments='') # Connect to Arduino a = TCLab() # Final time tf = 10 # min # number of data points (every 3 seconds) n = tf * 20 + 1 # Configure heater levels # Percent Heater (0-100%) Q1s = np.zeros(n) Q2s = np.zeros(n) # Heater random steps every 50 sec # Alternate steps by Q1 and Q2 # with rapid, random changes every 10 cycles for i in range(n): if i%20==0: Q1s[i:i+20] = random.random() * 100.0 if (i+10)%20==0: Q2s[i:i+20] = random.random() * 100.0 # heater 2 initially off Q2s[0:50] = 0.0 # heater 1 off at end (last 50 cycles) Q1s[-50:-1] = 0.0 # Record initial temperatures (degC) T1m = a.T1 * np.ones(n) T2m = a.T2 * np.ones(n) # Store MHE values for plots Tmhe1 = T1m[0] * np.ones(n) Tmhe2 = T2m[0] * np.ones(n) K1s = 0.5 * np.ones(n) K2s = 0.3 * np.ones(n) K3s = 0.005 * np.ones(n) tau12s = 150.0 * np.ones(n) tau3s = 5.0 * np.ones(n) ######################################################### # Initialize Model as Estimator ######################################################### m = GEKKO(name='tclab-mhe',remote=False) # 120 second time horizon, 40 steps m.time = np.linspace(0,120,41) # Parameters to Estimate K1 = m.FV(value=0.5) K1.STATUS = 0 K1.FSTATUS = 0 K1.DMAX = 0.1 K1.LOWER = 0.1 K1.UPPER = 1.0 K2 = m.FV(value=0.3) K2.STATUS = 0 K2.FSTATUS = 0 K2.DMAX = 0.1 K2.LOWER = 0.1 K2.UPPER = 1.0 K3 = m.FV(value=0.2) K3.STATUS = 0 K3.FSTATUS = 0 K3.DMAX = 0.01 K3.LOWER = 0.1 K3.UPPER = 1.0 tau12 = m.FV(value=150) tau12.STATUS = 0 tau12.FSTATUS = 0 tau12.DMAX = 5.0 tau12.LOWER = 50.0 tau12.UPPER = 200 tau3 = m.FV(value=15) tau3.STATUS = 0 tau3.FSTATUS = 0 tau3.DMAX = 1 tau3.LOWER = 10 tau3.UPPER = 20 # Measured inputs Q1 = m.MV(value=0) Q1.FSTATUS = 1 # measured Q2 = m.MV(value=0) Q2.FSTATUS = 1 # measured # State variables TH1 = m.SV(value=T1m[0]) TH2 = m.SV(value=T2m[0]) # Measurements for model alignment TC1 = m.CV(value=T1m[0]) TC1.STATUS = 1 # minimize error TC1.FSTATUS = 1 # receive measurement TC1.MEAS_GAP = 0.1 # measurement deadband gap TC2 = m.CV(value=T2m[0]) TC2.STATUS = 1 # minimize error TC2.FSTATUS = 1 # receive measurement TC2.MEAS_GAP = 0.1 # measurement deadband gap Ta = m.Param(value=23.0) # degC # Heat transfer between two heaters DT = m.Intermediate(TH2-TH1) # Empirical correlations m.Equation(tau12 * TH1.dt() + (TH1-Ta) == K1*Q1 + K3*DT) m.Equation(tau12 * TH2.dt() + (TH2-Ta) == K2*Q2 - K3*DT) m.Equation(tau3 * TC1.dt() + TC1 == TH1) m.Equation(tau3 * TC2.dt() + TC2 == TH2) # Global Options m.options.IMODE = 5 # MHE m.options.EV_TYPE = 1 # Objective type m.options.NODES = 3 # Collocation nodes m.options.SOLVER = 3 # IPOPT m.options.COLDSTART = 1 # COLDSTART on first cycle ################################################################## # Create plot plt.figure(figsize=(10,7)) plt.ion() plt.show() # Main Loop start_time = time.time() prev_time = start_time tm = np.zeros(n) try: for i in range(1,n): # Sleep time sleep_max = 3.0 sleep = sleep_max - (time.time() - prev_time) if sleep>=0.01: time.sleep(sleep-0.01) else: time.sleep(0.01) # Record time and change in time t = time.time() dt = t - prev_time prev_time = t tm[i] = t - start_time # Read temperatures in Celsius T1m[i] = a.T1 T2m[i] = a.T2 # Insert measurements TC1.MEAS = T1m[i] TC2.MEAS = T2m[i] Q1.MEAS = Q1s[i-1] Q2.MEAS = Q2s[i-1] # Start estimating U after 10 cycles (20 sec) if i==10: K1.STATUS = 1 K2.STATUS = 1 K3.STATUS = 1 tau12.STATUS = 1 tau3.STATUS = 1 # Predict Parameters and Temperatures with MHE # use remote=False for local solve m.solve() if m.options.APPSTATUS == 1: # Retrieve new values Tmhe1[i] = TC1.MODEL Tmhe2[i] = TC2.MODEL K1s[i] = K1.NEWVAL K2s[i] = K2.NEWVAL K3s[i] = K3.NEWVAL tau12s[i] = tau12.NEWVAL tau3s[i] = tau3.NEWVAL else: # Solution failed, copy prior solution Tmhe1[i] = Tmhe1[i-1] Tmhe2[i] = Tmhe1[i-1] K1s[i] = K1s[i-1] K2s[i] = K2s[i-1] K3s[i] = K3s[i-1] tau12s[i] = tau12s[i-1] tau3s[i] = tau3s[i-1] # Write new heater values (0-100) a.Q1(Q1s[i]) a.Q2(Q2s[i]) # Plot plt.clf() ax=plt.subplot(4,1,1) ax.grid() plt.plot(tm[0:i],T1m[0:i],'ro',label=r'$T_1$ measured') plt.plot(tm[0:i],Tmhe1[0:i],'k-',label=r'$T_1$ MHE') plt.plot(tm[0:i],T2m[0:i],'bx',label=r'$T_2$ measured') plt.plot(tm[0:i],Tmhe2[0:i],'k--',label=r'$T_2$ MHE') plt.ylabel('Temperature (degC)') plt.legend(loc=2) ax=plt.subplot(4,1,2) ax.grid() plt.plot(tm[0:i],K1s[0:i],'k-',label='K1') plt.plot(tm[0:i],K2s[0:i],'g:',label='K2') plt.plot(tm[0:i],K3s[0:i]*100,'r--',label='K3 x 100') plt.ylabel('Gains') plt.legend(loc='best') ax=plt.subplot(4,1,3) ax.grid() plt.plot(tm[0:i],tau12s[0:i],'b-',label=r'$\tau_{12}$') plt.plot(tm[0:i],tau3s[0:i]*10,'r--',label=r'$\tau_3$ x 10') plt.ylabel('Time constant') plt.legend(loc='best') ax=plt.subplot(4,1,4) ax.grid() plt.plot(tm[0:i],Q1s[0:i],'r-',label=r'$Q_1$') plt.plot(tm[0:i],Q2s[0:i],'b:',label=r'$Q_2$') plt.ylabel('Heaters') plt.xlabel('Time (sec)') plt.legend(loc='best') plt.draw() plt.pause(0.05) # Turn off heaters a.Q1(0) a.Q2(0) save_txt(tm,Q1s,Q2s,T1m,T2m) # Save figure plt.savefig('tclab_mhe.png') # Allow user to end loop with Ctrl-C except KeyboardInterrupt: # Disconnect from Arduino a.Q1(0) a.Q2(0) print('Shutting down') a.close() plt.savefig('tclab_mhe.png') # Make sure serial connection still closes when there's an error except: # Disconnect from Arduino a.Q1(0) a.Q2(0) print('Error: Shutting down') a.close() plt.savefig('tclab_mhe.png') raise