H-Infinity Disturbance-Attenuating Latent Observer / observer_experiment.py
Failed on benchmark
1import json
2import numpy as np
3from scipy.linalg import eigvalsh
4
5# Scalar latent plant: z_dot = A z, y = C z + w.
6# The observation disturbance w enters the observer error through -K w.
7A, C = -0.4, 1.0
8alpha, gamma = 0.2, 0.9
9
10
11def lmi_matrix(K, P):
12 Ac = A - K * C
13 Bc = -K # Bw=0, Dw=1
14 Ce, De = 1.0, 0.0 # performance output q=e
15 return np.array([[2 * Ac * P + Ce**2 + 2 * alpha * P,
16 P * Bc + Ce * De],
17 [P * Bc + Ce * De,
18 De**2 - gamma**2]])
19
20
21def best_storage(K):
22 # Search the one-dimensional positive storage metric, as a transparent
23 # numerical analogue of optimizing P=LL^T+p_min I.
24 ps = np.logspace(-4, 3, 600)
25 vals = np.array([eigvalsh(lmi_matrix(K, p))[-1] for p in ps])
26 i = int(vals.argmin())
27 return float(ps[i]), float(vals[i])
28
29
30def exact_error(K, e0, times, w):
31 # Piecewise-constant forcing, exact propagation over each small interval.
32 e = np.empty(len(times)); e[0] = e0
33 dt = times[1] - times[0]
34 Ac = A - K
35 for j in range(len(times) - 1):
36 # e_dot = Ac e - K w[j]
37 e[j+1] = np.exp(Ac*dt)*e[j] + (-K*w[j]) * np.expm1(Ac*dt)/Ac
38 return e
39
40
41def dissipation_check(K, seed=7):
42 rng = np.random.default_rng(seed)
43 dt, T = 0.001, 8.0
44 t = np.arange(0, T + dt, dt)
45 # Smooth, piecewise disturbance, including an impulsive-like short pulse.
46 w = 0.35 * rng.standard_normal(len(t)-1)
47 w = np.convolve(w, np.ones(20)/20, mode='same')
48 w[1000:1030] += 5.0
49 e0 = 1.3
50 e = exact_error(K, e0, t, w)
51 P, lmax = best_storage(K)
52 V = P * e**2
53 lhs = (V[-1] - V[0] + np.sum((2*alpha*V[:-1] + e[:-1]**2 - gamma**2*w**2)*dt))
54 # Direct induced-energy ratio (excluding initial-condition energy).
55 zero = exact_error(K, 0.0, t, w)
56 ratio = np.sum(zero[:-1]**2)*dt / (np.sum(w**2)*dt)
57 return dict(P=P, lmi_max_eigenvalue=lmax, dissipation_residual=float(lhs),
58 induced_energy_ratio=float(ratio), initial_V=float(V[0]),
59 final_V=float(V[-1]))
60
61
62def exact_hinf_gain(K):
63 # For e_dot=-(A_abs+K)e-Kw, q=e, the stable transfer is -K/(s+0.4+K).
64 # Its H-infinity norm is attained at zero frequency.
65 return float(K / (K - A))
66
67
68def zero_disturbance_decay(K):
69 dt, T, e0 = 0.002, 8.0, 1.0
70 t = np.arange(0, T+dt, dt)
71 e = exact_error(K, e0, t, np.zeros(len(t)-1))
72 # Compare observed decay exponent with required V decay exponent 2 alpha.
73 P, _ = best_storage(K)
74 V = P*e**2
75 observed_rate = -np.log(V[-1]/V[0]) / T
76 return dict(observed_V_decay_rate=float(observed_rate), required_rate=2*alpha,
77 V_initial=float(V[0]), V_final=float(V[-1]))
78
79
80def main():
81 # Baseline: unconstrained high-gain innovation correction.
82 baseline_K = 10.0
83 # Idea: choose the largest gain on a coarse grid that is LMI feasible,
84 # then refine by the storage search. This is a tiny proxy for training K,P.
85 candidates = np.linspace(0.05, 10.0, 300)
86 feasible = [(K, best_storage(K)) for K in candidates if best_storage(K)[1] <= 1e-8]
87 idea_K, (idea_P, idea_lmi) = feasible[-1]
88 result = {
89 'config': {'A': A, 'C': C, 'alpha': alpha, 'gamma': gamma},
90 'baseline': {'K': baseline_K, 'lmi': best_storage(baseline_K),
91 'exact_hinf_gain': exact_hinf_gain(baseline_K),
92 'zero_disturbance': zero_disturbance_decay(baseline_K),
93 'disturbance': dissipation_check(baseline_K)},
94 'idea': {'K': float(idea_K), 'lmi': (idea_P, idea_lmi),
95 'exact_hinf_gain': exact_hinf_gain(idea_K),
96 'zero_disturbance': zero_disturbance_decay(idea_K),
97 'disturbance': dissipation_check(idea_K)},
98 'claim_check': {
99 'idea_lmi_feasible': bool(idea_lmi <= 1e-8),
100 'baseline_lmi_feasible': bool(best_storage(baseline_K)[1] <= 1e-8),
101 'idea_dissipation_holds': bool(dissipation_check(idea_K)['dissipation_residual'] <= 1e-5),
102 'idea_exact_hinf_below_gamma': bool(exact_hinf_gain(idea_K) <= gamma),
103 'baseline_exact_hinf_below_gamma': bool(exact_hinf_gain(baseline_K) <= gamma)
104 }
105 }
106 with open('results.json', 'w') as f:
107 json.dump(result, f, indent=2)
108 print(json.dumps(result, indent=2))
109
110if __name__ == '__main__':
111 main()