diff --git a/control/statesp.py b/control/statesp.py index 41f92ae21..99c5fe9dc 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -170,9 +170,9 @@ class StateSpace(LTI): The StateSpace class is used to represent state-space realizations of linear time-invariant (LTI) systems: - + .. math:: - + dx/dt &= A x + B u \\ y &= C x + D u @@ -1561,7 +1561,8 @@ def _convert_to_statespace(sys): return StateSpace( ssout[1][:states, :states], ssout[2][:states, :sys.ninputs], ssout[3][:sys.noutputs, :states], ssout[4], sys.dt, - inputs=sys.input_labels, outputs=sys.output_labels) + inputs=sys.input_labels, outputs=sys.output_labels, + name=sys.name) except ImportError: # No Slycot. Scipy tf->ss can't handle MIMO, but static # MIMO is an easy special case we can check for here @@ -1574,7 +1575,9 @@ def _convert_to_statespace(sys): for i, j in itertools.product(range(sys.noutputs), range(sys.ninputs)): D[i, j] = sys.num[i][j][0] / sys.den[i][j][0] - return StateSpace([], [], [], D, sys.dt) + return StateSpace([], [], [], D, sys.dt, + inputs=sys.input_labels, outputs=sys.output_labels, + name=sys.name) else: if sys.ninputs != 1 or sys.noutputs != 1: raise TypeError("No support for MIMO without slycot") @@ -1586,7 +1589,7 @@ def _convert_to_statespace(sys): sp.signal.tf2ss(squeeze(sys.num), squeeze(sys.den)) return StateSpace( A, B, C, D, sys.dt, inputs=sys.input_labels, - outputs=sys.output_labels) + outputs=sys.output_labels, name=sys.name) elif isinstance(sys, FrequencyResponseData): raise TypeError("Can't convert FRD to StateSpace system.") diff --git a/control/tests/interconnect_test.py b/control/tests/interconnect_test.py index 2c29aeaca..3d2f0c7d7 100644 --- a/control/tests/interconnect_test.py +++ b/control/tests/interconnect_test.py @@ -68,8 +68,13 @@ def test_interconnect_implicit(): ki = ct.tf(random.uniform(1, 10), [1, 0]) C = ct.tf2io(kp + ki, inputs='e', outputs='u', name='C') + # same but static C2 + C2 = ct.tf(random.uniform(1, 10), 1, + inputs='e', outputs='u', name='C2') + # Block diagram computation Tss = ct.feedback(P * C, 1) + Tss2 = ct.feedback(P * C2, 1) # Construct the interconnection explicitly Tio_exp = ct.interconnect( @@ -93,6 +98,15 @@ def test_interconnect_implicit(): np.testing.assert_almost_equal(Tio_sum.C, Tss.C) np.testing.assert_almost_equal(Tio_sum.D, Tss.D) + # test whether signal names work for static system C2 + Tio_sum2 = ct.interconnect( + [C2, P, sumblk], inputs='r', outputs='y') + + np.testing.assert_almost_equal(Tio_sum2.A, Tss2.A) + np.testing.assert_almost_equal(Tio_sum2.B, Tss2.B) + np.testing.assert_almost_equal(Tio_sum2.C, Tss2.C) + np.testing.assert_almost_equal(Tio_sum2.D, Tss2.D) + # Setting connections to False should lead to an empty connection map empty = ct.interconnect( (C, P, sumblk), connections=False, inplist=['r'], outlist=['y']) @@ -237,17 +251,17 @@ def test_linear_interconnect(): ss_ctrl = ct.ss(1, 2, 1, 2, inputs='e', outputs='u') ss_plant = ct.ss(1, 2, 1, 2, inputs='u', outputs='y') nl_ctrl = ct.NonlinearIOSystem( - lambda t, x, u, params: x*x, + lambda t, x, u, params: x*x, lambda t, x, u, params: u*x, states=1, inputs='e', outputs='u') nl_plant = ct.NonlinearIOSystem( - lambda t, x, u, params: x*x, + lambda t, x, u, params: x*x, lambda t, x, u, params: u*x, states=1, inputs='u', outputs='y') assert isinstance(ct.interconnect((tf_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem) assert isinstance(ct.interconnect((ss_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem) assert isinstance(ct.interconnect((tf_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem) assert isinstance(ct.interconnect((ss_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem) - + assert ~isinstance(ct.interconnect((nl_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem) assert ~isinstance(ct.interconnect((nl_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem) assert ~isinstance(ct.interconnect((ss_ctrl, nl_plant), inputs='e', outputs='y'), ct.LinearIOSystem) diff --git a/control/tests/namedio_test.py b/control/tests/namedio_test.py index 4925e9790..3203214d6 100644 --- a/control/tests/namedio_test.py +++ b/control/tests/namedio_test.py @@ -90,8 +90,10 @@ def test_named_ss(): (lambda t, x, u, params: -x, None), {'inputs': 2, 'outputs':2, 'states':2}], [ct.ss, ([[1, 2], [3, 4]], [[0], [1]], [[1, 0]], 0), {}], + [ct.ss, ([], [], [], 3), {}], # static system [ct.StateSpace, ([[1, 2], [3, 4]], [[0], [1]], [[1, 0]], 0), {}], [ct.tf, ([1, 2], [3, 4, 5]), {}], + [ct.tf, (2, 3), {}], # static system [ct.TransferFunction, ([1, 2], [3, 4, 5]), {}], ]) def test_io_naming(fun, args, kwargs): @@ -112,7 +114,7 @@ def test_io_naming(fun, args, kwargs): assert sys_g.name == 'sys[0]' assert sys_g.input_labels == [f'u[{i}]' for i in range(sys_g.ninputs)] assert sys_g.output_labels == [f'y[{i}]' for i in range(sys_g.noutputs)] - if sys_g.nstates: + if sys_g.nstates is not None: assert sys_g.state_labels == [f'x[{i}]' for i in range(sys_g.nstates)] # @@ -128,7 +130,7 @@ def test_io_naming(fun, args, kwargs): sys_r.set_outputs(output_labels) assert sys_r.output_labels == output_labels - if sys_g.nstates: + if sys_g.nstates is not None: state_labels = [f'x{i}' for i in range(sys_g.nstates)] sys_r.set_states(state_labels) assert sys_r.state_labels == state_labels @@ -143,7 +145,7 @@ def test_io_naming(fun, args, kwargs): sys_k = fun(state_labels, output_labels, input_labels, name='mysys') elif sys_g.nstates is None: - # Don't pass state labels + # Don't pass state labels if TransferFunction sys_k = fun( *args, inputs=input_labels, outputs=output_labels, name='mysys') @@ -155,7 +157,7 @@ def test_io_naming(fun, args, kwargs): assert sys_k.name == 'mysys' assert sys_k.input_labels == input_labels assert sys_k.output_labels == output_labels - if sys_g.nstates: + if sys_g.nstates is not None: assert sys_k.state_labels == state_labels # @@ -193,6 +195,24 @@ def test_io_naming(fun, args, kwargs): assert sys_tf.input_labels == input_labels assert sys_tf.output_labels == output_labels + # + # Convert the system to a LinearIOSystem and make sure labels transfer + # + if not isinstance( + sys_r, (ct.FrequencyResponseData, ct.NonlinearIOSystem)) and \ + ct.slycot_check(): + sys_lio = ct.LinearIOSystem(sys_r) + assert sys_lio != sys_r + assert sys_lio.input_labels == input_labels + assert sys_lio.output_labels == output_labels + + # Reassign system and signal names + sys_lio = ct.LinearIOSystem( + sys_g, inputs=input_labels, outputs=output_labels, name='new') + assert sys_lio.name == 'new' + assert sys_lio.input_labels == input_labels + assert sys_lio.output_labels == output_labels + # Internal testing of StateSpace initialization def test_init_namedif(): @@ -221,14 +241,29 @@ def test_init_namedif(): # Test state space conversion def test_convert_to_statespace(): - # Set up the initial system - sys = ct.tf(ct.rss(2, 1, 1)) + # Set up the initial systems + sys = ct.tf(ct.rss(2, 1, 1), inputs='u', outputs='y', name='sys') + sys_static = ct.tf(1, 2, inputs='u', outputs='y', name='sys_static') + + # check that name, inputs, and outputs passed through + sys_new = ct.ss(sys) + assert sys_new.name == 'sys' + assert sys_new.input_labels == ['u'] + assert sys_new.output_labels == ['y'] + sys_new = ct.ss(sys_static) + assert sys_new.name == 'sys_static' + assert sys_new.input_labels == ['u'] + assert sys_new.output_labels == ['y'] # Make sure we can rename system name, inputs, outputs sys_new = ct.ss(sys, inputs='u', outputs='y', name='new') assert sys_new.name == 'new' assert sys_new.input_labels == ['u'] assert sys_new.output_labels == ['y'] + sys_new = ct.ss(sys_static, inputs='u', outputs='y', name='new') + assert sys_new.name == 'new' + assert sys_new.input_labels == ['u'] + assert sys_new.output_labels == ['y'] # Try specifying the state names (via low level test) with pytest.warns(UserWarning, match="non-unique state space realization"):
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: