@@ -1571,7 +1571,7 @@ def __init__(self, io_sys, ss_sys=None):
1571
1571
def input_output_response (
1572
1572
sys , T , U = 0. , X0 = 0 , params = {},
1573
1573
transpose = False , return_x = False , squeeze = None ,
1574
- solve_ivp_kwargs = {}, ** kwargs ):
1574
+ solve_ivp_kwargs = {}, t_eval = 'T' , ** kwargs ):
1575
1575
"""Compute the output response of a system to a given input.
1576
1576
1577
1577
Simulate a dynamical system with a given input and return its output
@@ -1654,50 +1654,57 @@ def input_output_response(
1654
1654
if kwargs .get ('solve_ivp_method' , None ):
1655
1655
if kwargs .get ('method' , None ):
1656
1656
raise ValueError ("ivp_method specified more than once" )
1657
- solve_ivp_kwargs ['method' ] = kwargs [ 'solve_ivp_method' ]
1657
+ solve_ivp_kwargs ['method' ] = kwargs . pop ( 'solve_ivp_method' )
1658
1658
1659
1659
# Set the default method to 'RK45'
1660
1660
if solve_ivp_kwargs .get ('method' , None ) is None :
1661
1661
solve_ivp_kwargs ['method' ] = 'RK45'
1662
1662
1663
+ # Make sure all input arguments got parsed
1664
+ if kwargs :
1665
+ raise TypeError ("unknown parameters %s" % kwargs )
1666
+
1663
1667
# Sanity checking on the input
1664
1668
if not isinstance (sys , InputOutputSystem ):
1665
1669
raise TypeError ("System of type " , type (sys ), " not valid" )
1666
1670
1667
1671
# Compute the time interval and number of steps
1668
1672
T0 , Tf = T [0 ], T [- 1 ]
1669
- n_steps = len (T )
1673
+ ntimepts = len (T )
1674
+
1675
+ # Figure out simulation times (t_eval)
1676
+ if solve_ivp_kwargs .get ('t_eval' ):
1677
+ if t_eval == 'T' :
1678
+ # Override the default with the solve_ivp keyword
1679
+ t_eval = solve_ivp_kwargs .pop ('t_eval' )
1680
+ else :
1681
+ raise ValueError ("t_eval specified more than once" )
1682
+ if isinstance (t_eval , str ) and t_eval == 'T' :
1683
+ # Use the input time points as the output time points
1684
+ t_eval = T
1670
1685
1671
1686
# Check and convert the input, if needed
1672
1687
# TODO: improve MIMO ninputs check (choose from U)
1673
1688
if sys .ninputs is None or sys .ninputs == 1 :
1674
- legal_shapes = [(n_steps ,), (1 , n_steps )]
1689
+ legal_shapes = [(ntimepts ,), (1 , ntimepts )]
1675
1690
else :
1676
- legal_shapes = [(sys .ninputs , n_steps )]
1691
+ legal_shapes = [(sys .ninputs , ntimepts )]
1677
1692
U = _check_convert_array (U , legal_shapes ,
1678
1693
'Parameter ``U``: ' , squeeze = False )
1679
- U = U .reshape (- 1 , n_steps )
1680
-
1681
- # Check to make sure this is not a static function
1682
- nstates = _find_size (sys .nstates , X0 )
1683
- if nstates == 0 :
1684
- # No states => map input to output
1685
- u = U [0 ] if len (U .shape ) == 1 else U [:, 0 ]
1686
- y = np .zeros ((np .shape (sys ._out (T [0 ], X0 , u ))[0 ], len (T )))
1687
- for i in range (len (T )):
1688
- u = U [i ] if len (U .shape ) == 1 else U [:, i ]
1689
- y [:, i ] = sys ._out (T [i ], [], u )
1690
- return TimeResponseData (
1691
- T , y , None , U , issiso = sys .issiso (),
1692
- output_labels = sys .output_index , input_labels = sys .input_index ,
1693
- transpose = transpose , return_x = return_x , squeeze = squeeze )
1694
+ U = U .reshape (- 1 , ntimepts )
1695
+ ninputs = U .shape [0 ]
1694
1696
1695
1697
# create X0 if not given, test if X0 has correct shape
1698
+ nstates = _find_size (sys .nstates , X0 )
1696
1699
X0 = _check_convert_array (X0 , [(nstates ,), (nstates , 1 )],
1697
1700
'Parameter ``X0``: ' , squeeze = True )
1698
1701
1699
- # Update the parameter values
1700
- sys ._update_params (params )
1702
+ # Figure out the number of outputs
1703
+ if sys .noutputs is None :
1704
+ # Evaluate the output function to find number of outputs
1705
+ noutputs = np .shape (sys ._out (T [0 ], X0 , U [:, 0 ]))[0 ]
1706
+ else :
1707
+ noutputs = sys .noutputs
1701
1708
1702
1709
#
1703
1710
# Define a function to evaluate the input at an arbitrary time
@@ -1714,6 +1721,31 @@ def ufun(t):
1714
1721
dt = (t - T [idx - 1 ]) / (T [idx ] - T [idx - 1 ])
1715
1722
return U [..., idx - 1 ] * (1. - dt ) + U [..., idx ] * dt
1716
1723
1724
+ # Check to make sure this is not a static function
1725
+ if nstates == 0 : # No states => map input to output
1726
+ # Make sure the user gave a time vector for evaluation (or 'T')
1727
+ if t_eval is None :
1728
+ # User overrode t_eval with None, but didn't give us the times...
1729
+ warn ("t_eval set to None, but no dynamics; using T instead" )
1730
+ t_eval = T
1731
+
1732
+ # Allocate space for the inputs and outputs
1733
+ u = np .zeros ((ninputs , len (t_eval )))
1734
+ y = np .zeros ((noutputs , len (t_eval )))
1735
+
1736
+ # Compute the input and output at each point in time
1737
+ for i , t in enumerate (t_eval ):
1738
+ u [:, i ] = ufun (t )
1739
+ y [:, i ] = sys ._out (t , [], u [:, i ])
1740
+
1741
+ return TimeResponseData (
1742
+ t_eval , y , None , u , issiso = sys .issiso (),
1743
+ output_labels = sys .output_index , input_labels = sys .input_index ,
1744
+ transpose = transpose , return_x = return_x , squeeze = squeeze )
1745
+
1746
+ # Update the parameter values
1747
+ sys ._update_params (params )
1748
+
1717
1749
# Create a lambda function for the right hand side
1718
1750
def ivp_rhs (t , x ):
1719
1751
return sys ._rhs (t , x , ufun (t ))
@@ -1724,27 +1756,27 @@ def ivp_rhs(t, x):
1724
1756
raise NameError ("scipy.integrate.solve_ivp not found; "
1725
1757
"use SciPy 1.0 or greater" )
1726
1758
soln = sp .integrate .solve_ivp (
1727
- ivp_rhs , (T0 , Tf ), X0 , t_eval = T ,
1759
+ ivp_rhs , (T0 , Tf ), X0 , t_eval = t_eval ,
1728
1760
vectorized = False , ** solve_ivp_kwargs )
1761
+ if not soln .success :
1762
+ raise RuntimeError ("solve_ivp failed: " + soln .message )
1729
1763
1730
- if not soln .success or soln .status != 0 :
1731
- # Something went wrong
1732
- warn ("sp.integrate.solve_ivp failed" )
1733
- print ("Return bunch:" , soln )
1734
-
1735
- # Compute the output associated with the state (and use sys.out to
1736
- # figure out the number of outputs just in case it wasn't specified)
1737
- u = U [0 ] if len (U .shape ) == 1 else U [:, 0 ]
1738
- y = np .zeros ((np .shape (sys ._out (T [0 ], X0 , u ))[0 ], len (T )))
1739
- for i in range (len (T )):
1740
- u = U [i ] if len (U .shape ) == 1 else U [:, i ]
1741
- y [:, i ] = sys ._out (T [i ], soln .y [:, i ], u )
1764
+ # Compute inputs and outputs for each time point
1765
+ u = np .zeros ((ninputs , len (soln .t )))
1766
+ y = np .zeros ((noutputs , len (soln .t )))
1767
+ for i , t in enumerate (soln .t ):
1768
+ u [:, i ] = ufun (t )
1769
+ y [:, i ] = sys ._out (t , soln .y [:, i ], u [:, i ])
1742
1770
1743
1771
elif isdtime (sys ):
1772
+ # If t_eval was not specified, use the sampling time
1773
+ if t_eval is None :
1774
+ t_eval = np .arange (T [0 ], T [1 ] + sys .dt , sys .dt )
1775
+
1744
1776
# Make sure the time vector is uniformly spaced
1745
- dt = T [1 ] - T [0 ]
1746
- if not np .allclose (T [1 :] - T [:- 1 ], dt ):
1747
- raise ValueError ("Parameter ``T ``: time values must be "
1777
+ dt = t_eval [1 ] - t_eval [0 ]
1778
+ if not np .allclose (t_eval [1 :] - t_eval [:- 1 ], dt ):
1779
+ raise ValueError ("Parameter ``t_eval ``: time values must be "
1748
1780
"equally spaced." )
1749
1781
1750
1782
# Make sure the sample time matches the given time
@@ -1764,21 +1796,23 @@ def ivp_rhs(t, x):
1764
1796
1765
1797
# Compute the solution
1766
1798
soln = sp .optimize .OptimizeResult ()
1767
- soln .t = T # Store the time vector directly
1768
- x = X0 # Initilize state
1799
+ soln .t = t_eval # Store the time vector directly
1800
+ x = np . array ( X0 ) # State vector (store as floats)
1769
1801
soln .y = [] # Solution, following scipy convention
1770
- y = [] # System output
1771
- for i in range ( len ( T )) :
1772
- # Store the current state and output
1802
+ u , y = [], [] # System input, output
1803
+ for t in t_eval :
1804
+ # Store the current input, state, and output
1773
1805
soln .y .append (x )
1774
- y .append (sys ._out (T [i ], x , ufun (T [i ])))
1806
+ u .append (ufun (t ))
1807
+ y .append (sys ._out (t , x , u [- 1 ]))
1775
1808
1776
1809
# Update the state for the next iteration
1777
- x = sys ._rhs (T [ i ] , x , ufun ( T [ i ]) )
1810
+ x = sys ._rhs (t , x , u [ - 1 ] )
1778
1811
1779
1812
# Convert output to numpy arrays
1780
1813
soln .y = np .transpose (np .array (soln .y ))
1781
1814
y = np .transpose (np .array (y ))
1815
+ u = np .transpose (np .array (u ))
1782
1816
1783
1817
# Mark solution as successful
1784
1818
soln .success = True # No way to fail
@@ -1787,7 +1821,7 @@ def ivp_rhs(t, x):
1787
1821
raise TypeError ("Can't determine system type" )
1788
1822
1789
1823
return TimeResponseData (
1790
- soln .t , y , soln .y , U , issiso = sys .issiso (),
1824
+ soln .t , y , soln .y , u , issiso = sys .issiso (),
1791
1825
output_labels = sys .output_index , input_labels = sys .input_index ,
1792
1826
state_labels = sys .state_index ,
1793
1827
transpose = transpose , return_x = return_x , squeeze = squeeze )
0 commit comments