Skip to content

Commit c90ce5a

Browse files
committed
Add all Class07 lecture materials
1 parent 73b7ad5 commit c90ce5a

File tree

7 files changed

+5546
-0
lines changed

7 files changed

+5546
-0
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
np.random.seed(42)
5+
6+
V = 1.0; F1, F2 = 0.5, 0.3
7+
T1_nom, T2_nom = 350.0, 200.0; T_env = 298.0
8+
dt = 0.02; T_total = 50.0; N = int(T_total / dt)
9+
10+
# setpoint
11+
T_set = np.zeros(N)
12+
T_set[int(N*0.0):int(N*0.25)] = 330.0
13+
T_set[int(N*0.25):int(N*0.5)] = 210.0
14+
T_set[int(N*0.5):int(N*0.75)] = 340.0
15+
T_set[int(N*0.75):] = 320.0
16+
17+
# tuned parameters (these produced R2_uoc >= 0.9 in my run)
18+
reaction_coeff = 5e-5 # mild nonlinearity
19+
feed_noise_std = 0.1 # small disturbances
20+
21+
# robust baseline (weak, tightly clipped)
22+
K_robust = 9.0
23+
robust_limits = (-2.5, 2.5)
24+
25+
# Unscented-like controller (strong, predictive)
26+
K_uoc = 45.0
27+
alpha = 6.0
28+
sigma_std = 1.0
29+
uoc_limits = (-120.0, 120.0)
30+
31+
def reaction_rate(T):
32+
return reaction_coeff * (T - T_env) ** 2
33+
34+
def r2_manual(y_true, y_pred):
35+
ss_res = np.sum((y_true - y_pred) ** 2)
36+
ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)
37+
return 1 - ss_res / ss_tot if ss_tot != 0 else np.nan
38+
39+
T_robust = np.zeros(N); T_uoc = np.zeros(N); T_uoc_std = np.zeros(N)
40+
T_robust_current = 300.0; T_uoc_current = 300.0; T_uoc_std[0] = sigma_std
41+
robust_saturated = np.zeros(N)
42+
43+
for k in range(N - 1):
44+
T1 = T1_nom + np.random.randn() * feed_noise_std
45+
T2 = T2_nom + np.random.randn() * feed_noise_std
46+
47+
# Robust P (clipped)
48+
u_robust = K_robust * (T_set[k] - T_robust_current)
49+
u_robust = np.clip(u_robust, robust_limits[0], robust_limits[1])
50+
robust_saturated[k] = 1 if (u_robust <= robust_limits[0] or u_robust >= robust_limits[1]) else 0
51+
dT_robust = (F1*(T1 - T_robust_current) + F2*(T2 - T_robust_current)
52+
+ u_robust + reaction_rate(T_robust_current)) / V
53+
T_robust_current += dt * dT_robust
54+
T_robust[k + 1] = T_robust_current
55+
56+
# Unscented-like update
57+
def sigma_points(T):
58+
return np.array([T, T + sigma_std, T - sigma_std, T + 2*sigma_std, T - 2*sigma_std])
59+
def propagate_sigma(T_sigma, u, T1, T2):
60+
out = []
61+
for T in T_sigma:
62+
dT = (F1*(T1 - T) + F2*(T2 - T) + u + reaction_rate(T)) / V
63+
out.append(T + dt * dT)
64+
return np.array(out)
65+
66+
u_candidate = K_uoc * (T_set[k] - T_uoc_current)
67+
T_sigma = sigma_points(T_uoc_current)
68+
T_sigma_next = propagate_sigma(T_sigma, u_candidate, T1, T2)
69+
error_mean = np.mean(T_sigma_next) - T_set[k]
70+
u_final = np.clip(u_candidate - alpha * error_mean, uoc_limits[0], uoc_limits[1])
71+
std_next = np.std(T_sigma_next)
72+
73+
dT_uoc = (F1*(T1 - T_uoc_current) + F2*(T2 - T_uoc_current)
74+
+ u_final + reaction_rate(T_uoc_current)) / V
75+
T_uoc_current += dt * dT_uoc
76+
T_uoc[k + 1] = T_uoc_current
77+
T_uoc_std[k + 1] = std_next
78+
79+
r2_robust = r2_manual(T_set, T_robust)
80+
r2_uoc = r2_manual(T_set, T_uoc)
81+
rmse_robust = np.sqrt(np.mean((T_set - T_robust) ** 2))
82+
rmse_uoc = np.sqrt(np.mean((T_set - T_uoc) ** 2))
83+
84+
print(f"R² Robust control: {r2_robust:.4f}")
85+
print(f"R² Unscented control: {r2_uoc:.4f}")
86+
print(f"RMSE Robust: {rmse_robust:.3f} K, RMSE UOC: {rmse_uoc:.3f} K")
87+
print(f"Robust actuator saturation fraction: {robust_saturated.mean():.3f}")
88+
print("Max |T_robust|:", np.nanmax(np.abs(T_robust)))
89+
print("Max |T_uoc|:", np.nanmax(np.abs(T_uoc)))
90+
91+
# Plots
92+
plt.figure(figsize=(10,3))
93+
plt.plot(T_set, linestyle='--')
94+
plt.plot(T_robust)
95+
plt.plot(T_uoc)
96+
plt.fill_between(np.arange(len(T_uoc)), T_uoc - T_uoc_std, T_uoc + T_uoc_std, alpha=0.25)
97+
plt.legend(['Setpoint', 'Robust', 'UOC', 'UOC sigma'])
98+
plt.grid(True)
99+
plt.show()
100+

0 commit comments

Comments
 (0)