Skip to content

fix _isstatic() to use nstates==0 #790

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ def __init__(self, *args, init_namedio=True, **kwargs):
D = np.zeros((C.shape[0], B.shape[1]))
D = _ssmatrix(D)

# Matrices definining the linear system
# Matrices defining the linear system
self.A = A
self.B = B
self.C = C
Expand All @@ -346,9 +346,8 @@ def __init__(self, *args, init_namedio=True, **kwargs):
defaults = args[0] if len(args) == 1 else \
{'inputs': D.shape[1], 'outputs': D.shape[0],
'states': A.shape[0]}
static = (A.size == 0)
name, inputs, outputs, states, dt = _process_namedio_keywords(
kwargs, defaults, static=static, end=True)
kwargs, defaults, static=(A.size == 0), end=True)

# Initialize LTI (NamedIOSystem) object
super().__init__(
Expand Down Expand Up @@ -1484,11 +1483,6 @@ def output(self, t, x, u=None, params=None):
return (self.C @ x).reshape((-1,)) \
+ (self.D @ u).reshape((-1,)) # return as row vector

def _isstatic(self):
"""True if and only if the system has no dynamics, that is,
if A and B are zero. """
return not np.any(self.A) and not np.any(self.B)


# TODO: add discrete time check
def _convert_to_statespace(sys):
Expand Down
10 changes: 9 additions & 1 deletion control/tests/iosys_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class TSys:
[[-1, 1], [0, -2]], [[0, 1], [1, 0]],
[[1, 0], [0, 1]], np.zeros((2, 2)))

# Create a static gain linear system
T.staticgain = ct.StateSpace([], [], [], 1)

# Create simulation parameters
T.T = np.linspace(0, 10, 100)
T.U = np.sin(T.T)
Expand All @@ -51,7 +54,7 @@ class TSys:
def test_linear_iosys(self, tsys):
# Create an input/output system from the linear system
linsys = tsys.siso_linsys
iosys = ios.LinearIOSystem(linsys)
iosys = ios.LinearIOSystem(linsys).copy()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it obvious why this .copy is necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not obvious, but this gets rid of a warning later on since the interconnect function generates a warning if a system appears more than once in an interconnected system, since then the name of system is not unique. (It's also possible to turn the warning off with warn_dupicate=False).


# Make sure that the right hand side matches linear system
for x, u in (([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([0, 0], 1)):
Expand All @@ -66,6 +69,11 @@ def test_linear_iosys(self, tsys):
np.testing.assert_array_almost_equal(lti_t, ios_t)
np.testing.assert_allclose(lti_y, ios_y, atol=0.002, rtol=0.)

# Make sure that a static linear system has dt=None
# and otherwise dt is as specified
assert ios.LinearIOSystem(tsys.staticgain).dt is None
assert ios.LinearIOSystem(tsys.staticgain, dt=.1).dt == .1

def test_tf2io(self, tsys):
# Create a transfer function from the state space system
linsys = tsys.siso_linsys
Expand Down
34 changes: 14 additions & 20 deletions control/tests/statesp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,25 +399,6 @@ def test_freq_resp(self):
mag, phase, omega = sys.freqresp(true_omega)
np.testing.assert_almost_equal(mag, true_mag)

def test__isstatic(self):
A0 = np.zeros((2,2))
A1 = A0.copy()
A1[0,1] = 1.1
B0 = np.zeros((2,1))
B1 = B0.copy()
B1[0,0] = 1.3
C0 = A0
C1 = np.eye(2)
D0 = 0
D1 = np.ones((2,1))
assert StateSpace(A0, B0, C1, D1)._isstatic()
assert not StateSpace(A1, B0, C1, D1)._isstatic()
assert not StateSpace(A0, B1, C1, D1)._isstatic()
assert not StateSpace(A1, B1, C1, D1)._isstatic()
assert StateSpace(A0, B0, C0, D0)._isstatic()
assert StateSpace(A0, B0, C0, D1)._isstatic()
assert StateSpace(A0, B0, C1, D0)._isstatic()

@slycotonly
def test_minreal(self):
"""Test a minreal model reduction."""
Expand Down Expand Up @@ -1160,6 +1141,20 @@ def test_linfnorm_ct_mimo(self, ct_siso):
np.testing.assert_allclose(fpeak, reffpeak)


@pytest.mark.parametrize("args, static", [
(([], [], [], 1), True), # ctime, empty state
(([], [], [], 1, 1), True), # dtime, empty state
((0, 0, 0, 1), False), # ctime, unused state
((-1, 0, 0, 1), False), # ctime, exponential decay
((-1, 0, 0, 0), False), # ctime, no input, no output
((0, 0, 0, 1, 1), False), # dtime, integrator
((1, 0, 0, 1, 1), False), # dtime, unused state
((0, 0, 0, 1, None), False), # unspecified, unused state
])
def test_isstatic(args, static):
sys = ct.StateSpace(*args)
assert sys._isstatic() == static

# Make sure that using params for StateSpace objects generates a warning
def test_params_warning():
sys = StateSpace(-1, 1, 1, 0)
Expand All @@ -1169,4 +1164,3 @@ def test_params_warning():

with pytest.warns(UserWarning, match="params keyword ignored"):
sys.output(0, [0], [0], {'k': 5})