8888
8989
9090class TimeResponseData ():
91- """Class for returning time responses.
91+ """A class for returning time responses.
9292
9393 This class maintains and manipulates the data corresponding to the
9494 temporal response of an input/output system. It is used as the return
@@ -140,20 +140,18 @@ class TimeResponseData():
140140 performs squeeze processing.
141141
142142 squeeze : bool, optional
143- By default, if a system is single-input, single-output (SISO) then
144- the inputs and outputs are returned as a 1D array (indexed by time)
145- and if a system is multi-input or multi-output, then the inputs are
146- returned as a 2D array (indexed by input and time) and the outputs
147- are returned as either a 2D array (indexed by output and time) or a
148- 3D array (indexed by output, trace, and time). If ``squeeze=True``,
149- access to the output response will remove single-dimensional entries
150- from the shape of the inputs and outputs even if the system is not
151- SISO. If ``squeeze=False``, the input is returned as a 2D or 3D
152- array (indexed by the input [if multi-input], trace [if
153- multi-trace] and time) and the output as a 2D or 3D array (indexed
154- by the output, trace [if multi-trace], and time) even if the system
155- is SISO. The default value can be set using
156- config.defaults['control.squeeze_time_response'].
143+ By default, if a system is single-input, single-output (SISO)
144+ then the outputs (and inputs) are returned as a 1D array
145+ (indexed by time) and if a system is multi-input or
146+ multi-output, then the outputs are returned as a 2D array
147+ (indexed by output and time) or a 3D array (indexed by output,
148+ trace, and time). If ``squeeze=True``, access to the output
149+ response will remove single-dimensional entries from the shape
150+ of the inputs and outputs even if the system is not SISO. If
151+ ``squeeze=False``, the output is returned as a 2D or 3D array
152+ (indexed by the output [if multi-input], trace [if multi-trace]
153+ and time) even if the system is SISO. The default value can be
154+ set using config.defaults['control.squeeze_time_response'].
157155
158156 transpose : bool, optional
159157 If True, transpose all input and output arrays (for backward
@@ -183,6 +181,9 @@ class TimeResponseData():
183181 t, y = step_response(sys)
184182 t, y, x = step_response(sys, return_x=True)
185183
184+ When using this (legacy) interface, the state vector is not affected by
185+ the `squeeze` parameter.
186+
186187 2. For backward compatibility with earlier version of python-control,
187188 this class has ``__getitem__`` and ``__len__`` methods that allow the
188189 return value to be indexed:
@@ -191,11 +192,16 @@ class TimeResponseData():
191192 response[1]: returns the output vector
192193 response[2]: returns the state vector
193194
195+ When using this (legacy) interface, the state vector is not affected by
196+ the `squeeze` parameter.
197+
194198 3. The default settings for ``return_x``, ``squeeze`` and ``transpose``
195199 can be changed by calling the class instance and passing new values:
196200
197201 response(tranpose=True).input
198202
203+ See :meth:`TimeResponseData.__call__` for more information.
204+
199205 """
200206
201207 def __init__ (
@@ -251,8 +257,8 @@ def __init__(
251257 the system is SISO. The default value can be set using
252258 config.defaults['control.squeeze_time_response'].
253259
254- Additional parameters
255- ---------------------
260+ Other parameters
261+ ----------------
256262 transpose : bool, optional
257263 If True, transpose all input and output arrays (for backward
258264 compatibility with MATLAB and :func:`scipy.signal.lsim`).
@@ -391,8 +397,10 @@ def __init__(
391397 raise ValueError ("unknown squeeze value" )
392398 self .squeeze = squeeze
393399
394- # Store legacy keyword values (only needed for legacy interface)
400+ # Keep track of whether to transpose for MATLAB/scipy.signal
395401 self .transpose = transpose
402+
403+ # Store legacy keyword values (only needed for legacy interface)
396404 self .return_x = return_x
397405
398406 def __call__ (self , ** kwargs ):
@@ -405,13 +413,13 @@ def __call__(self, **kwargs):
405413 Parameters
406414 ----------
407415 squeeze : bool, optional
408- If squeeze=True, access to the output response will
409- remove single-dimensional entries from the shape of the inputs
410- and outputs even if the system is not SISO. If squeeze=False,
411- keep the input as a 2D or 3D array (indexed by the input (if
412- multi-input), trace (if single input) and time) and the output
413- as a 3D array (indexed by the output, trace, and time) even if
414- the system is SISO.
416+ If squeeze=True, access to the output response will remove
417+ single-dimensional entries from the shape of the inputs, outputs,
418+ and states even if the system is not SISO. If squeeze=False, keep
419+ the input as a 2D or 3D array (indexed by the input (if
420+ multi-input), trace (if single input) and time) and the output and
421+ states as a 3D array (indexed by the output/state , trace, and
422+ time) even if the system is SISO.
415423
416424 transpose : bool, optional
417425 If True, transpose all input and output arrays (for backward
@@ -421,13 +429,15 @@ def __call__(self, **kwargs):
421429 return_x : bool, optional
422430 If True, return the state vector when enumerating result by
423431 assigning to a tuple (default = False).
432+
424433 """
425434 # Make a copy of the object
426435 response = copy (self )
427436
428437 # Update any keywords that we were passed
429438 response .transpose = kwargs .pop ('transpose' , self .transpose )
430439 response .squeeze = kwargs .pop ('squeeze' , self .squeeze )
440+ response .return_x = kwargs .pop ('return_x' , self .squeeze )
431441
432442 # Make sure no unknown keywords were passed
433443 if len (kwargs ) != 0 :
@@ -452,32 +462,40 @@ def outputs(self):
452462
453463 Output response of the system, indexed by either the output and time
454464 (if only a single input is given) or the output, trace, and time
455- (for multiple traces).
465+ (for multiple traces). See :attr:`TimeResponseData.squeeze` for a
466+ description of how this can be modified using the `squeeze` keyword.
456467
457468 :type: 1D, 2D, or 3D array
469+
458470 """
459471 t , y = _process_time_response (
460472 self .t , self .y , issiso = self .issiso ,
461473 transpose = self .transpose , squeeze = self .squeeze )
462474 return y
463475
464- # Getter for state (implements non-standard squeeze processing)
476+ # Getter for states (implements squeeze processing)
465477 @property
466478 def states (self ):
467479 """Time response state vector.
468480
469481 Time evolution of the state vector, indexed indexed by either the
470- state and time (if only a single trace is given) or the state,
471- trace, and time (for multiple traces).
482+ state and time (if only a single trace is given) or the state, trace,
483+ and time (for multiple traces). See :attr:`TimeResponseData.squeeze`
484+ for a description of how this can be modified using the `squeeze`
485+ keyword.
472486
473487 :type: 2D or 3D array
474- """
475488
489+ """
476490 if self .x is None :
477491 return None
478492
493+ elif self .squeeze is True :
494+ x = self .x .squeeze ()
495+
479496 elif self .ninputs == 1 and self .noutputs == 1 and \
480- self .ntraces == 1 and self .x .ndim == 3 :
497+ self .ntraces == 1 and self .x .ndim == 3 and \
498+ self .squeeze is not False :
481499 # Single-input, single-output system with single trace
482500 x = self .x [:, 0 , :]
483501
@@ -491,7 +509,7 @@ def states(self):
491509
492510 return x
493511
494- # Getter for state (implements squeeze processing)
512+ # Getter for inputs (implements squeeze processing)
495513 @property
496514 def inputs (self ):
497515 """Time response input vector.
@@ -504,7 +522,12 @@ def inputs(self):
504522 the two. If a 3D vector is passed, then it represents a multi-trace,
505523 multi-input signal, indexed by input, trace, and time.
506524
525+ See :attr:`TimeResponseData.squeeze` for a description of how the
526+ dimensions of the input vector can be modified using the `squeeze`
527+ keyword.
528+
507529 :type: 1D or 2D array
530+
508531 """
509532 if self .u is None :
510533 return None
@@ -514,11 +537,45 @@ def inputs(self):
514537 transpose = self .transpose , squeeze = self .squeeze )
515538 return u
516539
540+ # Getter for legacy state (implements non-standard squeeze processing)
541+ @property
542+ def _legacy_states (self ):
543+ """Time response state vector (legacy version).
544+
545+ Time evolution of the state vector, indexed indexed by either the
546+ state and time (if only a single trace is given) or the state,
547+ trace, and time (for multiple traces).
548+
549+ The `legacy_states` property is not affected by the `squeeze` keyword
550+ and hence it will always have these dimensions.
551+
552+ :type: 2D or 3D array
553+
554+ """
555+
556+ if self .x is None :
557+ return None
558+
559+ elif self .ninputs == 1 and self .noutputs == 1 and \
560+ self .ntraces == 1 and self .x .ndim == 3 :
561+ # Single-input, single-output system with single trace
562+ x = self .x [:, 0 , :]
563+
564+ else :
565+ # Return the full set of data
566+ x = self .x
567+
568+ # Transpose processing
569+ if self .transpose :
570+ x = np .transpose (x , np .roll (range (x .ndim ), 1 ))
571+
572+ return x
573+
517574 # Implement iter to allow assigning to a tuple
518575 def __iter__ (self ):
519576 if not self .return_x :
520577 return iter ((self .time , self .outputs ))
521- return iter ((self .time , self .outputs , self .states ))
578+ return iter ((self .time , self .outputs , self ._legacy_states ))
522579
523580 # Implement (thin) getitem to allow access via legacy indexing
524581 def __getitem__ (self , index ):
@@ -533,7 +590,7 @@ def __getitem__(self, index):
533590 if index == 1 :
534591 return self .outputs
535592 if index == 2 :
536- return self .states
593+ return self ._legacy_states
537594 raise IndexError
538595
539596 # Implement (thin) len to emulate legacy testing interface
0 commit comments