Estimate hidden states
Test observability and build an observer for an unmeasured velocity. This module has 3 lessons and takes about 2h 25m. Each lesson has a practice exercise that runs real Python in your browser and a short quiz.
Start module 14 in the Learning LabObservability and sensor choice
Determine whether output history can reveal the full state.
Observability asks whether the initial state can be recovered from known inputs and output history. For a two-state system, stack C above C*A. Rank two means the state is observable in the ideal noise-free model.
For a double integrator with measured position, C=[1,0], output rate reveals velocity, so the system is observable. With only velocity measured, C=[0,1], an arbitrary constant position offset is invisible.
Controllability depends on A and B; observability depends on A and C. They answer different questions. Numerical rank also depends on scale and tolerance, so nearly dependent columns deserve attention even when a rank test passes.
See it in action
import numpy as np
A = np.array([[0.,1.],[0.,0.]])
for C in [np.array([[1.,0.]]),np.array([[0.,1.]])]:
O = np.vstack([C,C@A])
print('Sensor:',C,'Observability rank:',np.linalg.matrix_rank(O))
Choose sensors based on what state information they can reveal.
Luenberger observer dynamics
Place observer poles using the estimation error equation.
An observer predicts the state with the model, then corrects it using the innovation y-C*xhat: xhat_dot=A*xhat+B*u+L*(y-C*xhat). The innovation measures disagreement between measured and predicted output.
For exact noiseless models, error e=x-xhat obeys e_dot=(A-L*C)*e. Choose L so these poles decay. Duality lets place_poles(A.T,C.T,desired).gain_matrix.T compute L.
Very fast observer poles increase sensitivity to measurement noise and can require a smaller update period. A common initial choice is faster than controller poles, followed by noise and timing tests. Faster is not always better.
See it in action
import numpy as np
from scipy.signal import place_poles
A = np.array([[0.,1.],[0.,0.]])
C = np.array([[1.,0.]])
L = place_poles(A.T,C.T,[-4.,-5.]).gain_matrix.T
print('L:',L.ravel(),'Error poles:',np.linalg.eigvals(A-L@C))
Estimate hidden states by combining model prediction with measurement correction.
Practical: observe velocity from position
Compare convergence and noise sensitivity for two observer speeds.
Begin with true state [1,0] and estimate [0,0]. With no input, a double integrator holds constant position. The observer must infer that position and drive its velocity estimate back to zero.
Use dt=0.001 s and update true and estimated states from their old values. Then add seeded measurement noise to position and compare moderate and fast observer poles. Report root mean square estimation error after the initial transient.
In a simulation, true state is available for evaluation. In a physical system it normally is not. Do not accidentally use true velocity in the observer update; it would invalidate the experiment.
Your experiment plan
Use the browser editor or the downloadable notebook. All plant data are simulated. Keep a notebook with parameters, predictions, results, and units.
- Simulate noise-free estimation from a wrong initial estimate.
- Repeat with position noise from np.random.default_rng(42).
- Compare poles [-3,-4] and [-12,-16] using position and velocity RMSE after one second.
- Explain why fast convergence and low noisy error can favour different observers.
What to hand in
- True and estimated state plots.
- A convergence and noise RMSE table.
- A clear separation between sensor input and evaluation truth.
Reflect on your results
Which assumption matters most? Describe one result that surprised you and one experiment that would test your explanation.
The automatic exercise checks the core code behaviour. Review observations and written deliverables yourself or with a mentor.
See it in action
import numpy as np
from scipy.linalg import expm
A = np.array([[0.,1.],[0.,0.]])
C = np.array([[1.,0.]])
L = np.array([[7.],[12.]])
initial_error = np.array([1.,0.])
for t in [0.,1.,2.,4.]:
error = expm((A-L@C)*t)@initial_error
print(t,error)
Keep hidden truth for evaluation, not for the estimator input.
Free, open and run by a nonprofit.
This page is part of Control Engineering, a free course of 20 modules and 60 lessons in the engineering.fit( ) Learning Lab. The Learning Lab is built and run by engineering.fit( ), a nonprofit founded by Abdullateef Opeyemi Bakare that helps African engineering students gain research experience. You do not need an account or pay a fee to learn.