@@ -1571,7 +1571,7 @@ def __init__(self, io_sys, ss_sys=None):
15711571def input_output_response (
15721572sys ,T ,U = 0. ,X0 = 0 ,params = {},
15731573transpose = False ,return_x = False ,squeeze = None ,
1574- solve_ivp_kwargs = {},** kwargs ):
1574+ solve_ivp_kwargs = {},t_eval = 'T' , ** kwargs ):
15751575"""Compute the output response of a system to a given input.
15761576
15771577 Simulate a dynamical system with a given input and return its output
@@ -1654,50 +1654,57 @@ def input_output_response(
16541654if kwargs .get ('solve_ivp_method' ,None ):
16551655if kwargs .get ('method' ,None ):
16561656raise 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' )
16581658
16591659# Set the default method to 'RK45'
16601660if solve_ivp_kwargs .get ('method' ,None )is None :
16611661solve_ivp_kwargs ['method' ]= 'RK45'
16621662
1663+ # Make sure all input arguments got parsed
1664+ if kwargs :
1665+ raise TypeError ("unknown parameters %s" % kwargs )
1666+
16631667# Sanity checking on the input
16641668if not isinstance (sys ,InputOutputSystem ):
16651669raise TypeError ("System of type " ,type (sys )," not valid" )
16661670
16671671# Compute the time interval and number of steps
16681672T0 ,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
16701685
16711686# Check and convert the input, if needed
16721687# TODO: improve MIMO ninputs check (choose from U)
16731688if sys .ninputs is None or sys .ninputs == 1 :
1674- legal_shapes = [(n_steps ,), (1 ,n_steps )]
1689+ legal_shapes = [(ntimepts ,), (1 ,ntimepts )]
16751690else :
1676- legal_shapes = [(sys .ninputs ,n_steps )]
1691+ legal_shapes = [(sys .ninputs ,ntimepts )]
16771692U = _check_convert_array (U ,legal_shapes ,
16781693'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 ]
16941696
16951697# create X0 if not given, test if X0 has correct shape
1698+ nstates = _find_size (sys .nstates ,X0 )
16961699X0 = _check_convert_array (X0 , [(nstates ,), (nstates ,1 )],
16971700'Parameter ``X0``: ' ,squeeze = True )
16981701
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
17011708
17021709#
17031710# Define a function to evaluate the input at an arbitrary time
@@ -1714,6 +1721,31 @@ def ufun(t):
17141721dt = (t - T [idx - 1 ])/ (T [idx ]- T [idx - 1 ])
17151722return U [...,idx - 1 ]* (1. - dt )+ U [...,idx ]* dt
17161723
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+
17171749# Create a lambda function for the right hand side
17181750def ivp_rhs (t ,x ):
17191751return sys ._rhs (t ,x ,ufun (t ))
@@ -1724,27 +1756,27 @@ def ivp_rhs(t, x):
17241756raise NameError ("scipy.integrate.solve_ivp not found; "
17251757"use SciPy 1.0 or greater" )
17261758soln = sp .integrate .solve_ivp (
1727- ivp_rhs , (T0 ,Tf ),X0 ,t_eval = T ,
1759+ ivp_rhs , (T0 ,Tf ),X0 ,t_eval = t_eval ,
17281760vectorized = False ,** solve_ivp_kwargs )
1761+ if not soln .success :
1762+ raise RuntimeError ("solve_ivp failed: " + soln .message )
17291763
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 ])
17421770
17431771elif 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+
17441776# 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 "
17481780"equally spaced." )
17491781
17501782# Make sure the sample time matches the given time
@@ -1764,21 +1796,23 @@ def ivp_rhs(t, x):
17641796
17651797# Compute the solution
17661798soln = 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)
17691801soln .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 currentinput, state, and output
17731805soln .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 ]))
17751808
17761809# 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 ] )
17781811
17791812# Convert output to numpy arrays
17801813soln .y = np .transpose (np .array (soln .y ))
17811814y = np .transpose (np .array (y ))
1815+ u = np .transpose (np .array (u ))
17821816
17831817# Mark solution as successful
17841818soln .success = True # No way to fail
@@ -1787,7 +1821,7 @@ def ivp_rhs(t, x):
17871821raise TypeError ("Can't determine system type" )
17881822
17891823return TimeResponseData (
1790- soln .t ,y ,soln .y ,U ,issiso = sys .issiso (),
1824+ soln .t ,y ,soln .y ,u ,issiso = sys .issiso (),
17911825output_labels = sys .output_index ,input_labels = sys .input_index ,
17921826state_labels = sys .state_index ,
17931827transpose = transpose ,return_x = return_x ,squeeze = squeeze )