Skip to content

Commit e82670e

Browse files
committed
add examples for create_{statefbk,estimator}_iosystem + related fixes
1 parent 84079e0 commit e82670e

9 files changed

Lines changed: 321 additions & 31 deletions

File tree

control/mateqn.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,9 @@ def _slycot_or_scipy(method):
606606

607607
# Utility function to check matrix dimensions
608608
def _check_shape(M, n, m, square=False, symmetric=False, name="??"):
609-
if square and M.shape[0] != M.shape[1]:
609+
M = np.atleast_2d(M)
610+
611+
if (square or symmetric) and M.shape[0] != M.shape[1]:
610612
raise ControlDimension("%s must be a square matrix" % name)
611613

612614
if symmetric and not _is_symmetric(M):
@@ -617,6 +619,8 @@ def _check_shape(M, n, m, square=False, symmetric=False, name="??"):
617619
f"Incompatible dimensions of {name} matrix; "
618620
f"expected ({n}, {m}) but found {M.shape}")
619621

622+
return M
623+
620624

621625
# Utility function to check if a matrix is symmetric
622626
def _is_symmetric(M):

control/nlsys.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ def cxn_string(signal, gain, first):
873873
cxn, width=78, initial_indent=" * ",
874874
subsequent_indent=" ")) + "\n"
875875

876-
out += "\nOutputs:\n"
876+
out += "\nOutputs:"
877877
for i in range(len(self.output_labels)):
878878
first = True
879879
cxn = f"{self.output_labels[i]} <- "
@@ -883,9 +883,9 @@ def cxn_string(signal, gain, first):
883883
cxn += cxn_string(
884884
output_list[j], self.output_map[i, j], first)
885885
first = False
886-
out += "\n".join(textwrap.wrap(
886+
out += "\n" + "\n".join(textwrap.wrap(
887887
cxn, width=78, initial_indent=" * ",
888-
subsequent_indent=" ")) + "\n"
888+
subsequent_indent=" "))
889889

890890
return out
891891

control/statefbk.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -783,12 +783,12 @@ def create_statefbk_iosystem(
783783
if integral_action is not None:
784784
if not isinstance(integral_action, np.ndarray):
785785
raise ControlArgument("Integral action must pass an array")
786-
elif integral_action.shape[1] != sys_nstates:
786+
787+
C = np.atleast_2d(integral_action)
788+
if C.shape[1] != sys_nstates:
787789
raise ControlArgument(
788790
"Integral gain size must match system state size")
789-
else:
790-
nintegrators = integral_action.shape[0]
791-
C = integral_action
791+
nintegrators = C.shape[0]
792792
else:
793793
# Create a C matrix with no outputs, just in case update gets called
794794
C = np.zeros((0, sys_nstates))

control/statesp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,7 @@ def __call__(self, *args, **kwargs):
16081608
return StateSpace.__call__(self, *args, **kwargs)
16091609

16101610
def __str__(self):
1611-
string = InterconnectedSystem.__str__(self) + "\n"
1611+
string = InterconnectedSystem.__str__(self) + "\n\n"
16121612
string += "\n\n".join([
16131613
"{} = {}".format(Mvar,
16141614
"\n ".join(str(M).splitlines()))

control/stochsys.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,15 +321,15 @@ def create_estimator_iosystem(
321321
.. math::
322322
323323
d \hat{x}/dt &= A \hat{x} + B u - L (C \hat{x} - y) \\
324-
dP/dt &= A P + P A^T + F Q_N F^T - P C^T R_N^{-1} C P \\
324+
dP/dt &= A P + P A^T + G Q_N G^T - P C^T R_N^{-1} C P \\
325325
L &= P C^T R_N^{-1}
326326
327327
or a discrete-time state estimator of the form
328328
329329
.. math::
330330
331331
\hat{x}[k+1] &= A \hat{x}[k] + B u[k] - L (C \hat{x}[k] - y[k]) \\
332-
P[k+1] &= A P A^T + F Q_N F^T - A P C^T R_e^{-1} C P A \\
332+
P[k+1] &= A P A^T + G Q_N G^T - A P C^T R_e^{-1} C P A \\
333333
L &= A P C^T R_e^{-1}
334334
335335
where :math:`R_e = R_N + C P C^T`. It can be called in the form::
@@ -353,7 +353,7 @@ def create_estimator_iosystem(
353353
state covariance.
354354
G : ndarray, optional
355355
Disturbance matrix describing how the disturbances enters the
356-
dynamics. Defaults to sys.B.
356+
dynamics. Defaults to `sys.B`.
357357
C : ndarray, optional
358358
If the system has full state output, define the measured values to
359359
be used by the estimator. Otherwise, use the system output as the
@@ -430,6 +430,11 @@ def create_estimator_iosystem(
430430
resp = ct.input_output_response(
431431
est, T, 0, [X0, P0], param={'correct': False)
432432
433+
References
434+
----------
435+
.. [1] R. M. Murray, `Optimization-Based Control
436+
<https://fbswiki.org/OBC`_, 2013.
437+
433438
"""
434439

435440
# Make sure that we were passed an I/O system as an input
@@ -480,11 +485,13 @@ def create_estimator_iosystem(
480485
# Generate the disturbance matrix (G)
481486
if G is None:
482487
G = sys.B if len(dist_idx) == 0 else sys.B[:, dist_idx]
488+
G = _check_shape(G, sys.nstates, len(dist_idx), name='G')
483489

484490
# Initialize the covariance matrix
485491
if P0 is None:
486492
# Initalize P0 to the steady state value
487493
_, P0, _ = lqe(A, G, C, QN, RN)
494+
P0 = _check_shape(P0, sys.nstates, sys.nstates, symmetric=True, name='P0')
488495

489496
# Figure out the labels to use
490497
estimate_labels = _process_labels(
@@ -507,6 +514,10 @@ def create_estimator_iosystem(
507514
inputs = measurement_labels + control_labels if inputs is None \
508515
else inputs
509516

517+
# Process the disturbance covariances and check size
518+
QN = _check_shape(QN, G.shape[1], G.shape[1], square=True, name='QN')
519+
RN = _check_shape(RN, C.shape[0], C.shape[0], square=True, name='RN')
520+
510521
if isinstance(covariance_labels, str):
511522
# Generate the list of labels using the argument as a format string
512523
covariance_labels = [

control/tests/response_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_response_plot(respfcn, plotfcn, respargs):
6161
else:
6262
# Single argument is enough
6363
args = (ct.rss(4, 1, 1, strictly_proper=True), )
64-
64+
6565
# Standard calling pattern - generate response, then plot
6666
plt.figure()
6767
resp = respfcn(*args, **respargs)

0 commit comments

Comments
 (0)