Skip to content

Commit ae2b0d3

Browse files
committed
standardize use of squeeze and return_x in time response functions
1 parent 848112d commit ae2b0d3

File tree

11 files changed

+282
-109
lines changed

11 files changed

+282
-109
lines changed

control/frdata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ def eval(self, omega, squeeze=None):
398398
out = out[0][0]
399399
return out
400400

401-
def __call__(self, s, squeeze=True):
401+
def __call__(self, s, squeeze=None):
402402
"""Evaluate system's transfer function at complex frequencies.
403403
404404
Returns the complex frequency response `sys(s)` of system `sys` with

control/iosys.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ def __init__(self, io_sys, ss_sys=None):
13531353

13541354

13551355
def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
1356-
return_x=False, squeeze=True):
1356+
return_x=False, squeeze=None):
13571357

13581358
"""Compute the output response of a system to a given input.
13591359
@@ -1373,9 +1373,9 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
13731373
return_x : bool, optional
13741374
If True, return the values of the state at each time (default = False).
13751375
squeeze : bool, optional
1376-
If True (default), squeeze unused dimensions out of the output
1377-
response. In particular, for a single output system, return a
1378-
vector of shape (nsteps) instead of (nsteps, 1).
1376+
If True and if the system has a single output, return the
1377+
system output as a 1D array rather than a 2D array. Default
1378+
value (True) set by config.defaults['control.squeeze'].
13791379
13801380
Returns
13811381
-------
@@ -1398,6 +1398,9 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
13981398
if not isinstance(sys, InputOutputSystem):
13991399
raise TypeError("System of type ", type(sys), " not valid")
14001400

1401+
if squeeze is None:
1402+
squeeze = config.defaults['control.squeeze']
1403+
14011404
# Compute the time interval and number of steps
14021405
T0, Tf = T[0], T[-1]
14031406
n_steps = len(T)
@@ -1420,8 +1423,8 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
14201423
for i in range(len(T)):
14211424
u = U[i] if len(U.shape) == 1 else U[:, i]
14221425
y[:, i] = sys._out(T[i], [], u)
1423-
if squeeze:
1424-
y = np.squeeze(y)
1426+
if squeeze and y.shape[0] == 1:
1427+
y = y[0]
14251428
if return_x:
14261429
return T, y, []
14271430
else:
@@ -1501,8 +1504,8 @@ def ivp_rhs(t, x): return sys._rhs(t, x, u(t))
15011504
raise TypeError("Can't determine system type")
15021505

15031506
# Get rid of extra dimensions in the output, of desired
1504-
if squeeze:
1505-
y = np.squeeze(y)
1507+
if squeeze and y.shape[0] == 1:
1508+
y = y[0]
15061509

15071510
if return_x:
15081511
return soln.t, y, soln.y

control/matlab/timeresp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,5 +273,6 @@ def lsim(sys, U=0., T=None, X0=0.):
273273
>>> yout, T, xout = lsim(sys, U, T, X0)
274274
'''
275275
from ..timeresp import forced_response
276-
T, yout, xout = forced_response(sys, T, U, X0, transpose = True)
276+
T, yout, xout = forced_response(
277+
sys, T, U, X0, return_x=True, transpose=True)
277278
return yout, T, xout

control/statesp.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,9 +646,9 @@ def __call__(self, x, squeeze=None):
646646
Returns the complex frequency response `sys(x)` where `x` is `s` for
647647
continuous-time systems and `z` for discrete-time systems.
648648
649-
In general the system may be multiple input, multiple output (MIMO), where
650-
`m = self.inputs` number of inputs and `p = self.outputs` number of
651-
outputs.
649+
In general the system may be multiple input, multiple output
650+
(MIMO), where `m = self.inputs` number of inputs and `p =
651+
self.outputs` number of outputs.
652652
653653
To evaluate at a frequency omega in radians per second, enter
654654
``x = omega * 1j``, for continuous-time systems, or

control/tests/discrete_test.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,15 @@ def testSimulation(self, tsys):
353353
tout, yout = step_response(tsys.siso_ss1d, T)
354354
tout, yout = impulse_response(tsys.siso_ss1d)
355355
tout, yout = impulse_response(tsys.siso_ss1d, T)
356-
tout, yout, xout = forced_response(tsys.siso_ss1d, T, U, 0)
357-
tout, yout, xout = forced_response(tsys.siso_ss2d, T, U, 0)
358-
tout, yout, xout = forced_response(tsys.siso_ss3d, T, U, 0)
356+
tout, yout = forced_response(tsys.siso_ss1d, T, U, 0)
357+
tout, yout = forced_response(tsys.siso_ss2d, T, U, 0)
358+
tout, yout = forced_response(tsys.siso_ss3d, T, U, 0)
359+
tout, yout, xout = forced_response(
360+
tsys.siso_ss1d, T, U, 0, return_x=True)
361+
tout, yout, xout = forced_response(
362+
tsys.siso_ss2d, T, U, 0, return_x=True)
363+
tout, yout, xout = forced_response(
364+
tsys.siso_ss3d, T, U, 0, return_x=True)
359365

360366
def test_sample_system(self, tsys):
361367
# Make sure we can convert various types of systems

control/tests/flatsys_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def test_double_integrator(self, xf, uf, Tf):
4848
T = np.linspace(0, Tf, 100)
4949
xd, ud = traj.eval(T)
5050

51-
t, y, x = ct.forced_response(sys, T, ud, x1)
51+
t, y, x = ct.forced_response(sys, T, ud, x1, return_x=True)
5252
np.testing.assert_array_almost_equal(x, xd, decimal=3)
5353

5454
def test_kinematic_car(self):

control/tests/iosys_test.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_linear_iosys(self, tsys):
6161

6262
# Make sure that simulations also line up
6363
T, U, X0 = tsys.T, tsys.U, tsys.X0
64-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0)
64+
lti_t, lti_y = ct.forced_response(linsys, T, U, X0)
6565
ios_t, ios_y = ios.input_output_response(iosys, T, U, X0)
6666
np.testing.assert_array_almost_equal(lti_t, ios_t)
6767
np.testing.assert_allclose(lti_y, ios_y, atol=0.002, rtol=0.)
@@ -75,7 +75,7 @@ def test_tf2io(self, tsys):
7575

7676
# Verify correctness via simulation
7777
T, U, X0 = tsys.T, tsys.U, tsys.X0
78-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0)
78+
lti_t, lti_y = ct.forced_response(linsys, T, U, X0)
7979
ios_t, ios_y = ios.input_output_response(iosys, T, U, X0)
8080
np.testing.assert_array_almost_equal(lti_t, ios_t)
8181
np.testing.assert_allclose(lti_y, ios_y, atol=0.002, rtol=0.)
@@ -84,7 +84,7 @@ def test_tf2io(self, tsys):
8484
tfsys = ct.tf('s')
8585
with pytest.raises(ValueError):
8686
iosys=ct.tf2io(tfsys)
87-
87+
8888
def test_ss2io(self, tsys):
8989
# Create an input/output system from the linear system
9090
linsys = tsys.siso_linsys
@@ -162,7 +162,7 @@ def test_nonlinear_iosys(self, tsys):
162162

163163
# Make sure that simulations also line up
164164
T, U, X0 = tsys.T, tsys.U, tsys.X0
165-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0)
165+
lti_t, lti_y = ct.forced_response(linsys, T, U, X0)
166166
ios_t, ios_y = ios.input_output_response(nlsys, T, U, X0)
167167
np.testing.assert_array_almost_equal(lti_t, ios_t)
168168
np.testing.assert_allclose(lti_y, ios_y,atol=0.002,rtol=0.)
@@ -256,7 +256,7 @@ def test_connect(self, tsys):
256256
X0 = np.concatenate((tsys.X0, tsys.X0))
257257
ios_t, ios_y, ios_x = ios.input_output_response(
258258
iosys_series, T, U, X0, return_x=True)
259-
lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
259+
lti_t, lti_y = ct.forced_response(linsys_series, T, U, X0)
260260
np.testing.assert_array_almost_equal(lti_t, ios_t)
261261
np.testing.assert_allclose(lti_y, ios_y,atol=0.002,rtol=0.)
262262

@@ -273,7 +273,7 @@ def test_connect(self, tsys):
273273
assert ct.isctime(iosys_series, strict=True)
274274
ios_t, ios_y, ios_x = ios.input_output_response(
275275
iosys_series, T, U, X0, return_x=True)
276-
lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
276+
lti_t, lti_y = ct.forced_response(linsys_series, T, U, X0)
277277
np.testing.assert_array_almost_equal(lti_t, ios_t)
278278
np.testing.assert_allclose(lti_y, ios_y,atol=0.002,rtol=0.)
279279

@@ -288,7 +288,7 @@ def test_connect(self, tsys):
288288
)
289289
ios_t, ios_y, ios_x = ios.input_output_response(
290290
iosys_feedback, T, U, X0, return_x=True)
291-
lti_t, lti_y, lti_x = ct.forced_response(linsys_feedback, T, U, X0)
291+
lti_t, lti_y = ct.forced_response(linsys_feedback, T, U, X0)
292292
np.testing.assert_array_almost_equal(lti_t, ios_t)
293293
np.testing.assert_allclose(lti_y, ios_y,atol=0.002,rtol=0.)
294294

@@ -325,7 +325,8 @@ def test_connect_spec_variants(self, tsys, connections, inplist, outlist):
325325
# Create a simulation run to compare against
326326
T, U = tsys.T, tsys.U
327327
X0 = np.concatenate((tsys.X0, tsys.X0))
328-
lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
328+
lti_t, lti_y, lti_x = ct.forced_response(
329+
linsys_series, T, U, X0, return_x=True)
329330

330331
# Create the input/output system with different parameter variations
331332
iosys_series = ios.InterconnectedSystem(
@@ -360,7 +361,8 @@ def test_connect_spec_warnings(self, tsys, connections, inplist, outlist):
360361
# Create a simulation run to compare against
361362
T, U = tsys.T, tsys.U
362363
X0 = np.concatenate((tsys.X0, tsys.X0))
363-
lti_t, lti_y, lti_x = ct.forced_response(linsys_series, T, U, X0)
364+
lti_t, lti_y, lti_x = ct.forced_response(
365+
linsys_series, T, U, X0, return_x=True)
364366

365367
# Set up multiple gainst and make sure a warning is generated
366368
with pytest.warns(UserWarning, match="multiple.*Combining"):
@@ -388,7 +390,8 @@ def test_static_nonlinearity(self, tsys):
388390

389391
# Make sure saturation works properly by comparing linear system with
390392
# saturated input to nonlinear system with saturation composition
391-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, Usat, X0)
393+
lti_t, lti_y, lti_x = ct.forced_response(
394+
linsys, T, Usat, X0, return_x=True)
392395
ios_t, ios_y, ios_x = ios.input_output_response(
393396
ioslin * nlsat, T, U, X0, return_x=True)
394397
np.testing.assert_array_almost_equal(lti_t, ios_t)
@@ -424,7 +427,7 @@ def test_algebraic_loop(self, tsys):
424427
# Nonlinear system composed with LTI system (series) -- with states
425428
ios_t, ios_y = ios.input_output_response(
426429
nlios * lnios * nlios, T, U, X0)
427-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U*U, X0)
430+
lti_t, lti_y, = ct.forced_response(linsys, T, U*U, X0)
428431
np.testing.assert_array_almost_equal(ios_y, lti_y*lti_y, decimal=3)
429432

430433
# Nonlinear system in feeback loop with LTI system
@@ -480,7 +483,7 @@ def test_summer(self, tsys):
480483
U = [np.sin(T), np.cos(T)]
481484
X0 = 0
482485

483-
lin_t, lin_y, lin_x = ct.forced_response(linsys_parallel, T, U, X0)
486+
lin_t, lin_y, = ct.forced_response(linsys_parallel, T, U, X0)
484487
ios_t, ios_y = ios.input_output_response(iosys_parallel, T, U, X0)
485488
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
486489

@@ -502,7 +505,7 @@ def test_rmul(self, tsys):
502505

503506
# Make sure we got the right thing (via simulation comparison)
504507
ios_t, ios_y = ios.input_output_response(sys2, T, U, X0)
505-
lti_t, lti_y, lti_x = ct.forced_response(ioslin, T, U*U, X0)
508+
lti_t, lti_y = ct.forced_response(ioslin, T, U*U, X0)
506509
np.testing.assert_array_almost_equal(ios_y, lti_y*lti_y, decimal=3)
507510

508511
@noscipy0
@@ -525,7 +528,7 @@ def test_neg(self, tsys):
525528

526529
# Make sure we got the right thing (via simulation comparison)
527530
ios_t, ios_y = ios.input_output_response(sys, T, U, X0)
528-
lti_t, lti_y, lti_x = ct.forced_response(ioslin, T, U*U, X0)
531+
lti_t, lti_y = ct.forced_response(ioslin, T, U*U, X0)
529532
np.testing.assert_array_almost_equal(ios_y, -lti_y, decimal=3)
530533

531534
@noscipy0
@@ -541,7 +544,7 @@ def test_feedback(self, tsys):
541544
linsys = ct.feedback(tsys.siso_linsys, 1)
542545

543546
ios_t, ios_y = ios.input_output_response(iosys, T, U, X0)
544-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0)
547+
lti_t, lti_y = ct.forced_response(linsys, T, U, X0)
545548
np.testing.assert_allclose(ios_y, lti_y,atol=0.002,rtol=0.)
546549

547550
@noscipy0
@@ -561,33 +564,33 @@ def test_bdalg_functions(self, tsys):
561564
# Series interconnection
562565
linsys_series = ct.series(linsys1, linsys2)
563566
iosys_series = ct.series(linio1, linio2)
564-
lin_t, lin_y, lin_x = ct.forced_response(linsys_series, T, U, X0)
567+
lin_t, lin_y = ct.forced_response(linsys_series, T, U, X0)
565568
ios_t, ios_y = ios.input_output_response(iosys_series, T, U, X0)
566569
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
567570

568571
# Make sure that systems don't commute
569572
linsys_series = ct.series(linsys2, linsys1)
570-
lin_t, lin_y, lin_x = ct.forced_response(linsys_series, T, U, X0)
573+
lin_t, lin_y = ct.forced_response(linsys_series, T, U, X0)
571574
assert not (np.abs(lin_y - ios_y) < 1e-3).all()
572575

573576
# Parallel interconnection
574577
linsys_parallel = ct.parallel(linsys1, linsys2)
575578
iosys_parallel = ct.parallel(linio1, linio2)
576-
lin_t, lin_y, lin_x = ct.forced_response(linsys_parallel, T, U, X0)
579+
lin_t, lin_y = ct.forced_response(linsys_parallel, T, U, X0)
577580
ios_t, ios_y = ios.input_output_response(iosys_parallel, T, U, X0)
578581
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
579582

580583
# Negation
581584
linsys_negate = ct.negate(linsys1)
582585
iosys_negate = ct.negate(linio1)
583-
lin_t, lin_y, lin_x = ct.forced_response(linsys_negate, T, U, X0)
586+
lin_t, lin_y = ct.forced_response(linsys_negate, T, U, X0)
584587
ios_t, ios_y = ios.input_output_response(iosys_negate, T, U, X0)
585588
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
586589

587590
# Feedback interconnection
588591
linsys_feedback = ct.feedback(linsys1, linsys2)
589592
iosys_feedback = ct.feedback(linio1, linio2)
590-
lin_t, lin_y, lin_x = ct.forced_response(linsys_feedback, T, U, X0)
593+
lin_t, lin_y = ct.forced_response(linsys_feedback, T, U, X0)
591594
ios_t, ios_y = ios.input_output_response(iosys_feedback, T, U, X0)
592595
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
593596

@@ -614,13 +617,13 @@ def test_nonsquare_bdalg(self, tsys):
614617
# Multiplication
615618
linsys_multiply = linsys_3i2o * linsys_2i3o
616619
iosys_multiply = iosys_3i2o * iosys_2i3o
617-
lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U2, X0)
620+
lin_t, lin_y = ct.forced_response(linsys_multiply, T, U2, X0)
618621
ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U2, X0)
619622
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
620623

621624
linsys_multiply = linsys_2i3o * linsys_3i2o
622625
iosys_multiply = iosys_2i3o * iosys_3i2o
623-
lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U3, X0)
626+
lin_t, lin_y = ct.forced_response(linsys_multiply, T, U3, X0)
624627
ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U3, X0)
625628
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
626629

@@ -633,7 +636,7 @@ def test_nonsquare_bdalg(self, tsys):
633636
# Feedback
634637
linsys_multiply = ct.feedback(linsys_3i2o, linsys_2i3o)
635638
iosys_multiply = iosys_3i2o.feedback(iosys_2i3o)
636-
lin_t, lin_y, lin_x = ct.forced_response(linsys_multiply, T, U3, X0)
639+
lin_t, lin_y = ct.forced_response(linsys_multiply, T, U3, X0)
637640
ios_t, ios_y = ios.input_output_response(iosys_multiply, T, U3, X0)
638641
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
639642

@@ -655,7 +658,7 @@ def test_discrete(self, tsys):
655658

656659
# Simulate and compare to LTI output
657660
ios_t, ios_y = ios.input_output_response(lnios, T, U, X0)
658-
lin_t, lin_y, lin_x = ct.forced_response(linsys, T, U, X0)
661+
lin_t, lin_y = ct.forced_response(linsys, T, U, X0)
659662
np.testing.assert_allclose(ios_t, lin_t,atol=0.002,rtol=0.)
660663
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
661664

@@ -671,7 +674,7 @@ def test_discrete(self, tsys):
671674

672675
# Simulate and compare to LTI output
673676
ios_t, ios_y = ios.input_output_response(lnios, T, U, X0)
674-
lin_t, lin_y, lin_x = ct.forced_response(linsys, T, U, X0)
677+
lin_t, lin_y = ct.forced_response(linsys, T, U, X0)
675678
np.testing.assert_allclose(ios_t, lin_t,atol=0.002,rtol=0.)
676679
np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.)
677680

@@ -839,7 +842,7 @@ def test_params(self, tsys):
839842
linsys = tsys.siso_linsys
840843
iosys = ios.LinearIOSystem(linsys)
841844
T, U, X0 = tsys.T, tsys.U, tsys.X0
842-
lti_t, lti_y, lti_x = ct.forced_response(linsys, T, U, X0)
845+
lti_t, lti_y = ct.forced_response(linsys, T, U, X0)
843846
with pytest.warns(UserWarning, match="LinearIOSystem.*ignored"):
844847
ios_t, ios_y = ios.input_output_response(
845848
iosys, T, U, X0, params={'something':0})

0 commit comments

Comments
 (0)