- Notifications
You must be signed in to change notification settings - Fork441
Implement ERA, change api to TimeResponseData#1024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
cd87f2f
8a9c497
562824c
6bbad5f
614a080
250c448
6c73062
8b93264
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -48,8 +48,9 @@ | ||
from .iosys import isdtime, isctime | ||
from .statesp import StateSpace | ||
from .statefbk import gram | ||
from .timeresp import TimeResponseData | ||
__all__ = ['hsvd', 'balred', 'modred', 'eigensys_realization', 'markov', 'minreal', 'era'] | ||
# Hankel Singular Value Decomposition | ||
@@ -368,38 +369,129 @@ def minreal(sys, tol=None, verbose=True): | ||
return sysr | ||
def _block_hankel(Y, m, n): | ||
"""Create a block Hankel matrix from impulse response""" | ||
q, p, _ = Y.shape | ||
YY = Y.transpose(0,2,1) # transpose for reshape | ||
H = np.zeros((q*m,p*n)) | ||
for r in range(m): | ||
# shift and add row to Hankel matrix | ||
new_row = YY[:,r:r+n,:] | ||
H[q*r:q*(r+1),:] = new_row.reshape((q,p*n)) | ||
return H | ||
def eigensys_realization(arg, r, m=None, n=None, dt=True, transpose=False): | ||
r"""eigensys_realization(YY, r) | ||
Calculate an ERA model of order `r` based on the impulse-response data | ||
`YY`. | ||
This function computes a discrete time system | ||
.. math:: | ||
x[k+1] &= A x[k] + B u[k] \\\\ | ||
y[k] &= C x[k] + D u[k] | ||
for a given impulse-response data (see [1]_). | ||
The function can be called with 2 arguments: | ||
* ``sysd, S = eigensys_realization(data, r)`` | ||
* ``sysd, S = eigensys_realization(YY, r)`` | ||
where `data` is a `TimeResponseData` object, `YY` is a 1D or 3D | ||
array, and r is an integer. | ||
Parameters | ||
---------- | ||
YY : array_like | ||
Impulse response from which the StateSpace model is estimated, 1D | ||
or 3D array. | ||
data : TimeResponseData | ||
Impulse response from which the StateSpace model is estimated. | ||
r : integer | ||
Order of model. | ||
m : integer, optional | ||
Number of rows in Hankel matrix. Default is 2*r. | ||
n : integer, optional | ||
Number of columns in Hankel matrix. Default is 2*r. | ||
dt : True or float, optional | ||
True indicates discrete time with unspecified sampling time and a | ||
positive float is discrete time with the specified sampling time. | ||
It can be used to scale the StateSpace model in order to match the | ||
unit-area impulse response of python-control. Default is True. | ||
transpose : bool, optional | ||
Assume that input data is transposed relative to the standard | ||
:ref:`time-series-convention`. For TimeResponseData this parameter | ||
is ignored. Default is False. | ||
Returns | ||
------- | ||
sys : StateSpace | ||
A reduced order model sys=StateSpace(Ar,Br,Cr,Dr,dt). | ||
S : array | ||
Singular values of Hankel matrix. Can be used to choose a good r | ||
value. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. given this, is there an alternative interface with a This probably a bit out-of-scope, and if the user needs this capability it is simple enough to implement in their own code. ContributorAuthor
| ||
References | ||
---------- | ||
.. [1] Samet Oymak and Necmiye Ozay, Non-asymptotic Identification of | ||
LTI Systems from a Single Trajectory. | ||
https://arxiv.org/abs/1806.05722 | ||
Examples | ||
-------- | ||
>>> T = np.linspace(0, 10, 100) | ||
>>> _, YY = ct.impulse_response(ct.tf([1], [1, 0.5], True), T) | ||
>>> sysd, _ = ct.eigensys_realization(YY, r=1) | ||
>>> T = np.linspace(0, 10, 100) | ||
>>> response = ct.impulse_response(ct.tf([1], [1, 0.5], True), T) | ||
>>> sysd, _ = ct.eigensys_realization(response, r=1) | ||
""" | ||
if isinstance(arg, TimeResponseData): | ||
YY = np.array(arg.outputs, ndmin=3) | ||
if arg.transpose: | ||
YY = np.transpose(YY) | ||
else: | ||
YY = np.array(arg, ndmin=3) | ||
if transpose: | ||
YY = np.transpose(YY) | ||
q, p, l = YY.shape | ||
if m is None: | ||
m = 2*r | ||
if n is None: | ||
n = 2*r | ||
if m*q < r or n*p < r: | ||
raise ValueError("Hankel parameters are to small") | ||
if (l-1) < m+n: | ||
raise ValueError("not enough data for requested number of parameters") | ||
H = _block_hankel(YY[:,:,1:], m, n+1) # Hankel matrix (q*m, p*(n+1)) | ||
Hf = H[:,:-p] # first p*n columns of H | ||
Hl = H[:,p:] # last p*n columns of H | ||
U,S,Vh = np.linalg.svd(Hf, True) | ||
Ur =U[:,0:r] | ||
Vhr =Vh[0:r,:] | ||
# balanced realizations | ||
Sigma_inv = np.diag(1./np.sqrt(S[0:r])) | ||
Ar = Sigma_inv @ Ur.T @ Hl @ Vhr.T @ Sigma_inv | ||
Br = Sigma_inv @ Ur.T @ Hf[:,0:p]*dt # dt scaling for unit-area impulse | ||
Cr = Hf[0:q,:] @ Vhr.T @ Sigma_inv | ||
Dr = YY[:,:,0] | ||
return StateSpace(Ar,Br,Cr,Dr,dt), S | ||
def markov(Y, U, m=None, transpose=False): | ||
@@ -556,3 +648,6 @@ def markov(Y, U, m=None, transpose=False): | ||
# Return the first m Markov parameters | ||
return H if transpose else np.transpose(H) | ||
# Function aliases | ||
era = eigensys_realization |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -137,7 +137,7 @@ Model simplification tools | ||
balred | ||
hsvd | ||
modred | ||
eigensys_realization | ||
markov | ||
Nonlinear system support | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../examples/era_msd.py |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
ERA example, mass spring damper system | ||
-------------------------------------- | ||
Code | ||
.... | ||
.. literalinclude:: era_msd.py | ||
:language: python | ||
:linenos: | ||
Notes | ||
..... | ||
1. The environment variable `PYCONTROL_TEST_EXAMPLES` is used for | ||
testing to turn off plotting of the outputs.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -35,6 +35,7 @@ other sources. | ||
kincar-flatsys | ||
mrac_siso_mit | ||
mrac_siso_lyapunov | ||
era_msd | ||
Jupyter notebooks | ||
================= | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# era_msd.py | ||
# Johannes Kaisinger, 4 July 2024 | ||
# | ||
# Demonstrate estimation of State Space model from impulse response. | ||
# SISO, SIMO, MISO, MIMO case | ||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
import os | ||
import control as ct | ||
# set up a mass spring damper system (2dof, MIMO case) | ||
# Mechanical Vibrations: Theory and Application, SI Edition, 1st ed. | ||
# Figure 6.5 / Example 6.7 | ||
# m q_dd + c q_d + k q = f | ||
m1, k1, c1 = 1., 4., 1. | ||
m2, k2, c2 = 2., 2., 1. | ||
k3, c3 = 6., 2. | ||
A = np.array([ | ||
[0., 0., 1., 0.], | ||
[0., 0., 0., 1.], | ||
[-(k1+k2)/m1, (k2)/m1, -(c1+c2)/m1, c2/m1], | ||
[(k2)/m2, -(k2+k3)/m2, c2/m2, -(c2+c3)/m2] | ||
]) | ||
B = np.array([[0.,0.],[0.,0.],[1/m1,0.],[0.,1/m2]]) | ||
C = np.array([[1.0, 0.0, 0.0, 0.0],[0.0, 1.0, 0.0, 0.0]]) | ||
D = np.zeros((2,2)) | ||
xixo_list = ["SISO","SIMO","MISO","MIMO"] | ||
xixo = xixo_list[3] # choose a system for estimation | ||
match xixo: | ||
case "SISO": | ||
sys = ct.StateSpace(A, B[:,0], C[0,:], D[0,0]) | ||
case "SIMO": | ||
sys = ct.StateSpace(A, B[:,:1], C, D[:,:1]) | ||
case "MISO": | ||
sys = ct.StateSpace(A, B, C[:1,:], D[:1,:]) | ||
case "MIMO": | ||
sys = ct.StateSpace(A, B, C, D) | ||
dt = 0.1 | ||
sysd = sys.sample(dt, method='zoh') | ||
response = ct.impulse_response(sysd) | ||
response.plot() | ||
plt.show() | ||
sysd_est, _ = ct.eigensys_realization(response,r=4,dt=dt) | ||
step_true = ct.step_response(sysd) | ||
step_true.sysname="H_true" | ||
step_est = ct.step_response(sysd_est) | ||
step_est.sysname="H_est" | ||
step_true.plot(title=xixo) | ||
step_est.plot(color='orange',linestyle='dashed') | ||
plt.show() | ||
if 'PYCONTROL_TEST_EXAMPLES' not in os.environ: | ||
plt.show() |
Uh oh!
There was an error while loading.Please reload this page.