10000 update squeeze processing for time responses + doc updates · python-control/python-control@47bbf16 · GitHub
[go: up one dir, main page]

Skip to content

Commit 47bbf16

Browse files
committed
update squeeze processing for time responses + doc updates
1 parent 05e6fe6 commit 47bbf16

File tree

5 files changed

+119
-51
lines changed

5 files changed

+119
-51
lines changed

control/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'control.default_dt': 0,
1919
'control.squeeze_frequency_response': None,
2020
'control.squeeze_time_response': True,
21+
'control.squeeze_time_response': None,
2122
'forced_response.return_x': False,
2223
}
2324
defaults = dict(_control_defaults)
@@ -236,4 +237,7 @@ def use_legacy_defaults(version):
236237
# forced_response no longer returns x by default
237238
set_defaults('forced_response', return_x=True)
238239

240+
# time responses are only squeezed if SISO
241+
set_defaults('control', squeeze_time_response=True)
242+
239243
return (major, minor, patch)

control/iosys.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,10 @@ def find_state(self, name):
450450
"""Find the index for a state given its name (`None` if not found)"""
451451
return self.state_index.get(name, None)
452452

453+
def issiso(self):
454+
"""Check to see if a system is single input, single output"""
455+
return self.ninputs == 1 and self.noutputs == 1
456+
453457
def feedback(self, other=1, sign=-1, params={}):
454458
"""Feedback interconnection between two input/output systems
455459
@@ -1373,18 +1377,22 @@ def input_output_response(sys, T, U=0., X0=0, params={}, method='RK45',
13731377
return_x : bool, optional
13741378
If True, return the values of the state at each time (default = False).
13751379
squeeze : bool, optional
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_time_response'].
1380+
If True and if the system has a single output, return the system
1381+
output as a 1D array rather than a 2D array. If False, return the
1382+
system output as a 2D array even if the system is SISO. Default value
1383+
set by config.defaults['control.squeeze_time_response'].
13791384
13801385
Returns
13811386
-------
13821387
T : array
13831388
Time values of the output.
13841389
yout : array
1385-
Response of the system.
1390+
Response of the system. If the system is SISO and squeeze is not
1391+
True, the array is 1D (indexed by time). If the system is not SISO or
1392+
squeeze is False, the array is 2D (indexed by the output number and
1393+
time).
13861394
xout : array
1387-
Time evolution of the state vector (if return_x=True)
1395+
Time evolution of the state vector (if return_x=True).
13881396
13891397
Raises
13901398
------

control/tests/iosys_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_nonlinear_iosys(self, tsys):
158158
nlout = lambda t, x, u, params: \
159159
np.reshape(np.dot(linsys.C, np.reshape(x, (-1, 1)))
160160
+ np.dot(linsys.D, u), (-1,))
161-
nlsys = ios.NonlinearIOSystem(nlupd, nlout)
161+
nlsys = ios.NonlinearIOSystem(nlupd, nlout, inputs=1, outputs=1)
162162

163163
# Make sure that simulations also line up
164164
T, U, X0 = tsys.T, tsys.U, tsys.X0

control/tests/timeresp_test.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -723,26 +723,22 @@ def test_time_series_data_convention_2D(self, siso_ss1):
723723
assert y.ndim == 1 # SISO returns "scalar" output
724724
assert t.shape == y.shape # Allows direct plotting of output
725725

726+
@pytest.mark.usefixtures("editsdefaults")
726727
@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.ss2io])
727728
@pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape", [
728729
[1, 1, 1, None, (8,)],
729730
[2, 1, 1, True, (8,)],
730731
[3, 1, 1, False, (1, 8)],
731-
# [4, 1, 1, 'siso', (8,)], # Use for later 'siso' implementation
732732
[3, 2, 1, None, (2, 8)],
733733
[4, 2, 1, True, (2, 8)],
734734
[5, 2, 1, False, (2, 8)],
735-
# [6, 2, 1, 'siso', (2, 8)], # Use for later 'siso' implementation
736-
[3, 1, 2, None, (8,)],
735+
[3, 1, 2, None, (1, 8)],
737736
[4, 1, 2, True, (8,)],
738737
[5, 1, 2, False, (1, 8)],
739-
# [6, 1, 2, 'siso', (1, 8)], # Use for later 'siso' implementation
740738
[4, 2, 2, None, (2, 8)],
741739
[5, 2, 2, True, (2, 8)],
742740
[6, 2, 2, False, (2, 8)],
743-
# [7, 2, 2, 'siso', (2, 8)], # Use for later 'siso' implementation
744741
])
745-
@pytest.mark.usefixtures("editsdefaults")
746742
def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape):
747743
# Figure out if we have SciPy 1+
748744
scipy0 = StrictVersion(sp.__version__) < '1.0'
@@ -789,7 +785,6 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape):
789785
_, yvec = ct.step_response(
790786
sys, tvec, input=ninp-1, output=nout-1, squeeze=squeeze)
791787
# Possible code if we implemenet a squeeze='siso' option
792-
# if squeeze is False or (squeeze == 'siso' and not sys.issiso()):
793788
if squeeze is False:
794789
# Shape should be unsqueezed
795790
assert yvec.shape == (1, 8)
@@ -836,3 +831,33 @@ def test_squeeze_exception(self, fcn):
836831
sys = fcn(ct.rss(2, 1, 1))
837832
with pytest.raises(ValueError, match="unknown squeeze value"):
838833
step_response(sys, squeeze=1)
834+
835+
@pytest.mark.usefixtures("editsdefaults")
836+
@pytest.mark.parametrize("nstate, nout, ninp, squeeze, shape", [
837+
[1, 1, 1, None, (8,)],
838+
[2, 1, 1, True, (8,)],
839+
[3, 1, 1, False, (1, 8)],
840+
[1, 2, 1, None, (2, 8)],
841+
[2, 2, 1, True, (2, 8)],
842+
[3, 2, 1, False, (2, 8)],
843+
[1, 1, 2, None, (8,)],
844+
[2, 1, 2, True, (8,)],
845+
[3, 1, 2, False, (1, 8)],
846+
[1, 2, 2, None, (2, 8)],
847+
[2, 2, 2, True, (2, 8)],
848+
[3, 2, 2, False, (2, 8)],
849+
])
850+
def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape):
851+
# Set defaults to match release 0.8.4
852+
ct.config.use_legacy_defaults('0.8.4')
853+
ct.config.use_numpy_matrix(False)
854+
855+
# Generate system, time, and input vectors
856+
sys = ct.rss(nstate, nout, ninp, strictly_proper=True)
857+
tvec = np.linspace(0, 1, 8)
858+
uvec = np.dot(
859+
np.ones((sys.inputs, 1)),
860+
np.reshape(np.sin(tvec), (1, 8)))
861+
862+
_, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze)
863+
assert yvec.shape == shape

control/timeresp.py

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -242,19 +242,27 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
242242
return only the time and output vectors.
243243
244244
squeeze : bool, optional
245-
If True (default), remove single-dimensional entries from the shape
246-
of the output. For single output systems, this converts the output
247-
response to a 1D array. The default value can be set using
245+
By default, if a system is single-input, single-output (SISO) then
246+
the output response is returned as a 1D array (indexed by time). If
247+
squeeze=True, remove single-dimensional entries from the shape of
248+
the output even if the system is not SISO. If squeeze=False, keep
249+
the output as a 2D array (indexed by the output number and time)
250+
even if the system is SISO. The default value can be set using
248251
config.defaults['control.squeeze_time_response'].
249252
250253
Returns
251254
-------
252255
T : array
253256
Time values of the output.
257+
254258
yout : array
255-
Response of the system.
259+
Response of the system. If the system is SISO and squeeze is not
260+
True, the array is 1D (indexed by time). If the system is not SISO or
261+
squeeze is False, the array is 2D (indexed by the output number and
262+
time).
263+
256264
xout : array
257-
Time evolution of the state vector.
265+
Time evolution of the state vector. Not affected by squeeze.
258266
259267
See Also
260268
--------
@@ -459,11 +467,9 @@ def _process_time_response(sys, tout, yout, xout, transpose=None,
459467
yout = np.squeeze(yout)
460468
elif squeeze is False: # squeeze no dimensions
461469
pass
462-
# Possible code if we implement a squeeze='siso' option
463-
# elif squeeze == 'siso': # squeeze signals if SISO
464-
# yout = yout[0] if sys.issiso() else yout
470+
elif squeeze is None: # squeeze signals if SISO
471+
yout = yout[0] if sys.issiso() else yout
465472
else:
466-
# In preparation for more complicated values for squeeze
467473
raise ValueError("unknown squeeze value")
468474

469475
# See if we need to transpose the data back into MATLAB form
@@ -481,13 +487,17 @@ def _get_ss_simo(sys, input=None, output=None, squeeze=None):
481487
482488
If input is not specified, select first input and issue warning
483489
"""
490+
# If squeeze was not specified, figure out the default
491+
if squeeze is None:
492+
squeeze = config.defaults['control.squeeze_time_response']
493+
484494
sys_ss = _convert_to_statespace(sys)
485495
if sys_ss.issiso():
486496
return squeeze, sys_ss
487-
# Possible code if we implement a squeeze='siso' option
488-
# elif squeeze == 'siso':
489-
# # Don't squeeze outputs if resulting system turns out to be siso
490-
# squeeze = False
497+
elif squeeze == None and (input is None or output is None):
498+
# Don't squeeze outputs if resulting system turns out to be siso
499+
# Note: if we expand input to allow a tuple, need to update this check
500+
squeeze = False
491501

492502
warn = False
493503
if input is None:
@@ -531,32 +541,34 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
531541
many simulation steps.
532542
533543
X0 : array_like or float, optional
534-
Initial condition (default = 0)
535-
536-
Numbers are converted to constant arrays with the correct shape.
544+
Initial condition (default = 0). Numbers are converted to constant
545+
arrays with the correct shape.
537546
538-
input : int
547+
input : int, optional
539548
Index of the input that will be used in this simulation. Default = 0.
540549
541-
output : int
550+
output : int, optional
542551
Index of the output that will be used in this simulation. Set to None
543552
to not trim outputs
544553
545554
T_num : int, optional
546555
Number of time steps to use in simulation if T is not provided as an
547556
array (autocomputed if not given); ignored if sys is discrete-time.
548557
549-
transpose : bool
558+
transpose : bool, optional
550559
If True, transpose all input and output arrays (for backward
551560
compatibility with MATLAB and :func:`scipy.signal.lsim`)
552561
553-
return_x : bool
562+
return_x : bool, optional
554563
If True, return the state vector (default = False).
555564
556565
squeeze : bool, optional
557-
If True (default), remove single-dimensional entries from the shape
558-
of the output. For single output systems, this converts the output
559-
response to a 1D array. The default value can be set using
566+
By default, if a system is single-input, single-output (SISO) then the
567+
output response is returned as a 1D array (indexed by time). If
568+
squeeze=True, remove single-dimensional entries from the shape of the
569+
output even if the system is not SISO. If squeeze=False, keep the
570+
output as a 2D array (indexed by the output number and time) even if
571+
the system is SISO. The default value can be set using
560572
config.defaults['control.squeeze_time_response'].
561573
562574
Returns
@@ -565,10 +577,13 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
565577
Time values of the output
566578
567579
yout : array
568-
Response of the system
580+
Response of the system. If the system is SISO and squeeze is not
581+
True, the array is 1D (indexed by time). If the system is not SISO or
582+
squeeze is False, the array is 2D (indexed by the output number and
583+
time).
569584
570585
xout : array, optional
571-
Individual response of each x variable (if return_x is True)
586+
Individual response of each x variable (if return_x is True).
572587
573588
See Also
574589
--------
@@ -722,19 +737,27 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None,
722737
If True, return the state vector (default = False).
723738
724739
squeeze : bool, optional
725-
If True (default), remove single-dimensional entries from the shape
726-
of the output. For single output systems, this converts the output
727-
response to a 1D array. The default value can be set using
740+
By default, if a system is single-input, single-output (SISO) then the
741+
output response is returned as a 1D array (indexed by time). If
742+
squeeze=True, remove single-dimensional entries from the shape of the
743+
output even if the system is not SISO. If squeeze=False, keep the
744+
output as a 2D array (indexed by the output number and time) even if
745+
the system is SISO. The default value can be set using
728746
config.defaults['control.squeeze_time_response'].
729747
730748
Returns
731749
-------
732750
T : array
733751
Time values of the output
752+
734753
yout : array
735-
Response of the system
754+
Response of the system. If the system is SISO and squeeze is not
755+
True, the array is 1D (indexed by time). If the system is not SISO or
756+
squeeze is False, the array is 2D (indexed by the output number and
757+
time).
758+
736759
xout : array, optional
737-
Individual response of each x variable (if return_x is True)
760+
Individual response of each x variable (if return_x is True).
738761
739762
See Also
740763
--------
@@ -789,10 +812,10 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
789812
790813
Numbers are converted to constant arrays with the correct shape.
791814
792-
input : int
815+
input : int, optional
793816
Index of the input that will be used in this simulation. Default = 0.
794817
795-
output : int
818+
output : int, optional
796819
Index of the output that will be used in this simulation. Set to None
797820
to not trim outputs
798821
@@ -804,23 +827,31 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None,
804827
If True, transpose all input and output arrays (for backward
805828
compatibility with MATLAB and :func:`scipy.signal.lsim`)
806829
807-
return_x : bool
830+
return_x : bool, optional
808831
If True, return the state vector (default = False).
809832
810833
squeeze : bool, optional
811-
If True (default), remove single-dimensional entries from the shape
812-
of the output. For single output systems, this converts the output
813-
response to a 1D array. The default value can be set using
834+
By default, if a system is single-input, single-output (SISO) then the
835+
output response is returned as a 1D array (indexed by time). If
836+
squeeze=True, remove single-dimensional entries from the shape of the
837+
output even if the system is not SISO. If squeeze=False, keep the
838+
output as a 2D array (indexed by the output number and time) even if
839+
the system is SISO. The default value can be set using
814840
config.defaults['control.squeeze_time_response'].
815841
816842
Returns
817843
-------
818844
T : array
819845
Time values of the output
846+
820847
yout : array
821-
Response of the system
848+
Response of the system. If the system is SISO and squeeze is not
849+
True, the array is 1D (indexed by time). If the system is not SISO or
850+
squeeze is False, the array is 2D (indexed by the output number and
851+
time).
852+
822853
xout : array, optional
823-
Individual response of each x variable (if return_x is True)
854+
Individual response of each x variable (if return_x is True).
824855
825856
See Also
826857
--------

0 commit comments

Comments
 (0)
0