Skip to content

Commit 184d83d

Browse files
authored
Merge pull request #884 from sawyerbfuller/static-signal-names
make _convert_to_statespace properly pass signal and system names
2 parents 0c46562 + 8b5eb47 commit 184d83d

File tree

3 files changed

+64
-12
lines changed

3 files changed

+64
-12
lines changed

control/statesp.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1564,7 +1564,8 @@ def _convert_to_statespace(sys):
15641564
return StateSpace(
15651565
ssout[1][:states, :states], ssout[2][:states, :sys.ninputs],
15661566
ssout[3][:sys.noutputs, :states], ssout[4], sys.dt,
1567-
inputs=sys.input_labels, outputs=sys.output_labels)
1567+
inputs=sys.input_labels, outputs=sys.output_labels,
1568+
name=sys.name)
15681569
except ImportError:
15691570
# No Slycot. Scipy tf->ss can't handle MIMO, but static
15701571
# MIMO is an easy special case we can check for here
@@ -1577,7 +1578,9 @@ def _convert_to_statespace(sys):
15771578
for i, j in itertools.product(range(sys.noutputs),
15781579
range(sys.ninputs)):
15791580
D[i, j] = sys.num[i][j][0] / sys.den[i][j][0]
1580-
return StateSpace([], [], [], D, sys.dt)
1581+
return StateSpace([], [], [], D, sys.dt,
1582+
inputs=sys.input_labels, outputs=sys.output_labels,
1583+
name=sys.name)
15811584
else:
15821585
if sys.ninputs != 1 or sys.noutputs != 1:
15831586
raise TypeError("No support for MIMO without slycot")
@@ -1589,7 +1592,7 @@ def _convert_to_statespace(sys):
15891592
sp.signal.tf2ss(squeeze(sys.num), squeeze(sys.den))
15901593
return StateSpace(
15911594
A, B, C, D, sys.dt, inputs=sys.input_labels,
1592-
outputs=sys.output_labels)
1595+
outputs=sys.output_labels, name=sys.name)
15931596

15941597
elif isinstance(sys, FrequencyResponseData):
15951598
raise TypeError("Can't convert FRD to StateSpace system.")

control/tests/interconnect_test.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@ def test_interconnect_implicit():
6868
ki = ct.tf(random.uniform(1, 10), [1, 0])
6969
C = ct.tf2io(kp + ki, inputs='e', outputs='u', name='C')
7070

71+
# same but static C2
72+
C2 = ct.tf(random.uniform(1, 10), 1,
73+
inputs='e', outputs='u', name='C2')
74+
7175
# Block diagram computation
7276
Tss = ct.feedback(P * C, 1)
77+
Tss2 = ct.feedback(P * C2, 1)
7378

7479
# Construct the interconnection explicitly
7580
Tio_exp = ct.interconnect(
@@ -93,6 +98,15 @@ def test_interconnect_implicit():
9398
np.testing.assert_almost_equal(Tio_sum.C, Tss.C)
9499
np.testing.assert_almost_equal(Tio_sum.D, Tss.D)
95100

101+
# test whether signal names work for static system C2
102+
Tio_sum2 = ct.interconnect(
103+
[C2, P, sumblk], inputs='r', outputs='y')
104+
105+
np.testing.assert_almost_equal(Tio_sum2.A, Tss2.A)
106+
np.testing.assert_almost_equal(Tio_sum2.B, Tss2.B)
107+
np.testing.assert_almost_equal(Tio_sum2.C, Tss2.C)
108+
np.testing.assert_almost_equal(Tio_sum2.D, Tss2.D)
109+
96110
# Setting connections to False should lead to an empty connection map
97111
empty = ct.interconnect(
98112
(C, P, sumblk), connections=False, inplist=['r'], outlist=['y'])
@@ -237,17 +251,17 @@ def test_linear_interconnect():
237251
ss_ctrl = ct.ss(1, 2, 1, 2, inputs='e', outputs='u')
238252
ss_plant = ct.ss(1, 2, 1, 2, inputs='u', outputs='y')
239253
nl_ctrl = ct.NonlinearIOSystem(
240-
lambda t, x, u, params: x*x,
254+
lambda t, x, u, params: x*x,
241255
lambda t, x, u, params: u*x, states=1, inputs='e', outputs='u')
242256
nl_plant = ct.NonlinearIOSystem(
243-
lambda t, x, u, params: x*x,
257+
lambda t, x, u, params: x*x,
244258
lambda t, x, u, params: u*x, states=1, inputs='u', outputs='y')
245259

246260
assert isinstance(ct.interconnect((tf_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
247261
assert isinstance(ct.interconnect((ss_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
248262
assert isinstance(ct.interconnect((tf_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
249263
assert isinstance(ct.interconnect((ss_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
250-
264+
251265
assert ~isinstance(ct.interconnect((nl_ctrl, ss_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
252266
assert ~isinstance(ct.interconnect((nl_ctrl, tf_plant), inputs='e', outputs='y'), ct.LinearIOSystem)
253267
assert ~isinstance(ct.interconnect((ss_ctrl, nl_plant), inputs='e', outputs='y'), ct.LinearIOSystem)

control/tests/namedio_test.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ def test_named_ss():
9090
(lambda t, x, u, params: -x, None),
9191
{'inputs': 2, 'outputs':2, 'states':2}],
9292
[ct.ss, ([[1, 2], [3, 4]], [[0], [1]], [[1, 0]], 0), {}],
93+
[ct.ss, ([], [], [], 3), {}], # static system
9394
[ct.StateSpace, ([[1, 2], [3, 4]], [[0], [1]], [[1, 0]], 0), {}],
9495
[ct.tf, ([1, 2], [3, 4, 5]), {}],
96+
[ct.tf, (2, 3), {}], # static system
9597
[ct.TransferFunction, ([1, 2], [3, 4, 5]), {}],
9698
])
9799
def test_io_naming(fun, args, kwargs):
@@ -112,7 +114,7 @@ def test_io_naming(fun, args, kwargs):
112114
assert sys_g.name == 'sys[0]'
113115
assert sys_g.input_labels == [f'u[{i}]' for i in range(sys_g.ninputs)]
114116
assert sys_g.output_labels == [f'y[{i}]' for i in range(sys_g.noutputs)]
115-
if sys_g.nstates:
117+
if sys_g.nstates is not None:
116118
assert sys_g.state_labels == [f'x[{i}]' for i in range(sys_g.nstates)]
117119

118120
#
@@ -128,7 +130,7 @@ def test_io_naming(fun, args, kwargs):
128130
sys_r.set_outputs(output_labels)
129131
assert sys_r.output_labels == output_labels
130132

131-
if sys_g.nstates:
133+
if sys_g.nstates is not None:
132134
state_labels = [f'x{i}' for i in range(sys_g.nstates)]
133135
sys_r.set_states(state_labels)
134136
assert sys_r.state_labels == state_labels
@@ -143,7 +145,7 @@ def test_io_naming(fun, args, kwargs):
143145
sys_k = fun(state_labels, output_labels, input_labels, name='mysys')
144146

145147
elif sys_g.nstates is None:
146-
# Don't pass state labels
148+
# Don't pass state labels if TransferFunction
147149
sys_k = fun(
148150
*args, inputs=input_labels, outputs=output_labels, name='mysys')
149151

@@ -155,7 +157,7 @@ def test_io_naming(fun, args, kwargs):
155157
assert sys_k.name == 'mysys'
156158
assert sys_k.input_labels == input_labels
157159
assert sys_k.output_labels == output_labels
158-
if sys_g.nstates:
160+
if sys_g.nstates is not None:
159161
assert sys_k.state_labels == state_labels
160162

161163
#
@@ -193,6 +195,24 @@ def test_io_naming(fun, args, kwargs):
193195
assert sys_tf.input_labels == input_labels
194196
assert sys_tf.output_labels == output_labels
195197

198+
#
199+
# Convert the system to a LinearIOSystem and make sure labels transfer
200+
#
201+
if not isinstance(
202+
sys_r, (ct.FrequencyResponseData, ct.NonlinearIOSystem)) and \
203+
ct.slycot_check():
204+
sys_lio = ct.LinearIOSystem(sys_r)
205+
assert sys_lio != sys_r
206+
assert sys_lio.input_labels == input_labels
207+
assert sys_lio.output_labels == output_labels
208+
209+
# Reassign system and signal names
210+
sys_lio = ct.LinearIOSystem(
211+
sys_g, inputs=input_labels, outputs=output_labels, name='new')
212+
assert sys_lio.name == 'new'
213+
assert sys_lio.input_labels == input_labels
214+
assert sys_lio.output_labels == output_labels
215+
196216

197217
# Internal testing of StateSpace initialization
198218
def test_init_namedif():
@@ -221,14 +241,29 @@ def test_init_namedif():
221241

222242
# Test state space conversion
223243
def test_convert_to_statespace():
224-
# Set up the initial system
225-
sys = ct.tf(ct.rss(2, 1, 1))
244+
# Set up the initial systems
245+
sys = ct.tf(ct.rss(2, 1, 1), inputs='u', outputs='y', name='sys')
246+
sys_static = ct.tf(1, 2, inputs='u', outputs='y', name='sys_static')
247+
248+
# check that name, inputs, and outputs passed through
249+
sys_new = ct.ss(sys)
250+
assert sys_new.name == 'sys'
251+
assert sys_new.input_labels == ['u']
252+
assert sys_new.output_labels == ['y']
253+
sys_new = ct.ss(sys_static)
254+
assert sys_new.name == 'sys_static'
255+
assert sys_new.input_labels == ['u']
256+
assert sys_new.output_labels == ['y']
226257

227258
# Make sure we can rename system name, inputs, outputs
228259
sys_new = ct.ss(sys, inputs='u', outputs='y', name='new')
229260
assert sys_new.name == 'new'
230261
assert sys_new.input_labels == ['u']
231262
assert sys_new.output_labels == ['y']
263+
sys_new = ct.ss(sys_static, inputs='u', outputs='y', name='new')
264+
assert sys_new.name == 'new'
265+
assert sys_new.input_labels == ['u']
266+
assert sys_new.output_labels == ['y']
232267

233268
# Try specifying the state names (via low level test)
234269
with pytest.warns(UserWarning, match="non-unique state space realization"):

0 commit comments

Comments
 (0)