@@ -249,8 +249,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords):
249
249
LTI system to simulate
250
250
251
251
T: array-like
252
- Time steps at which the input is defined, numbers must be (strictly
253
- monotonic) increasing.
252
+ Time steps at which the input is defined; values must be evenly spaced.
254
253
255
254
U: array-like or number, optional
256
255
Input array giving input at each time `T` (default = 0).
@@ -298,6 +297,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords):
298
297
# d_type = A.dtype
299
298
n_states = A .shape [0 ]
300
299
n_inputs = B .shape [1 ]
300
+ n_outputs = C .shape [0 ]
301
301
302
302
# Set and/or check time vector in discrete time case
303
303
if isdtime (sys , strict = True ):
@@ -323,28 +323,31 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, **keywords):
323
323
T = _check_convert_array (T , [('any' ,), (1 , 'any' )],
324
324
'Parameter ``T``: ' , squeeze = True ,
325
325
transpose = transpose )
326
- if not all ( T [1 : ] - T [: - 1 ] > 0 ):
327
- raise ValueError ( 'Parameter ``T``: time values must be '
328
- '(strictly monotonic) increasing numbers .' )
326
+ dt = T [1 ] - T [0 ]
327
+ if not np . allclose ( T [ 1 :] - T [: - 1 ], dt ):
328
+ raise ValueError ( 'Parameter ``T``: time values must be equally spaced .' )
329
329
n_steps = len (T ) # number of simulation steps
330
330
331
331
# create X0 if not given, test if X0 has correct shape
332
332
X0 = _check_convert_array (X0 , [(n_states ,), (n_states , 1 )],
333
333
'Parameter ``X0``: ' , squeeze = True )
334
334
335
+ xout = np .zeros ((n_states , n_steps ))
336
+ xout [:, 0 ] = X0
337
+ yout = np .zeros ((n_outputs , n_steps ))
338
+
335
339
# Separate out the discrete and continuous time cases
336
340
if isctime (sys ):
337
341
# Solve the differential equation, copied from scipy.signal.ltisys.
338
342
dot , squeeze , = np .dot , np .squeeze # Faster and shorter code
339
343
340
344
# Faster algorithm if U is zero
341
345
if U is None or (isinstance (U , (int , float )) and U == 0 ):
342
- # Function that computes the time derivative of the linear system
343
- def f_dot (x , _t ):
344
- return dot (A , x )
345
-
346
- xout = sp .integrate .odeint (f_dot , X0 , T , ** keywords )
347
- yout = dot (C , xout .T )
346
+ # Solve using matrix exponential
347
+ expAdt = sp .linalg .expm (A * dt )
348
+ for i in range (1 , n_steps ):
349
+ xout [:, i ] = dot (expAdt , xout [:, i - 1 ])
350
+ yout = dot (C , xout )
348
351
349
352
# General algorithm that interpolates U in between output points
350
353
else :
@@ -354,26 +357,36 @@ def f_dot(x, _t):
354
357
U = _check_convert_array (U , legal_shapes ,
355
358
'Parameter ``U``: ' , squeeze = False ,
356
359
transpose = transpose )
357
- # convert 1D array to D2 array with only one row
360
+ # convert 1D array to 2D array with only one row
358
361
if len (U .shape ) == 1 :
359
362
U = U .reshape (1 , - 1 ) # pylint: disable=E1103
360
363
361
- # Create a callable that uses linear interpolation to
362
- # calculate the input at any time.
363
- compute_u = \
364
- sp .interpolate .interp1d (T , U , kind = 'linear' , copy = False ,
365
- axis = - 1 , bounds_error = False ,
366
- fill_value = 0 )
367
-
368
- # Function that computes the time derivative of the linear system
369
- def f_dot (x , t ):
370
- return dot (A , x ) + squeeze (dot (B , compute_u ([t ])))
371
-
372
- xout = sp .integrate .odeint (f_dot , X0 , T , ** keywords )
373
- yout = dot (C , xout .T ) + dot (D , U )
364
+ # Algorithm: to integrate from time 0 to time dt, with linear
365
+ # interpolation between inputs u(0) = u0 and u(dt) = u1, we solve
366
+ # xdot = A x + B u, x(0) = x0
367
+ # udot = (u1 - u0) / dt, u(0) = u0.
368
+ #
369
+ # Solution is
370
+ # [ x(dt) ] [ A*dt B*dt 0 ] [ x0 ]
371
+ # [ u(dt) ] = exp [ 0 0 I ] [ u0 ]
372
+ # [u1 - u0] [ 0 0 0 ] [u1 - u0]
373
+
374
+ M = np .bmat ([[A * dt , B * dt , np .zeros ((n_states , n_inputs ))],
375
+ [np .zeros ((n_inputs , n_states + n_inputs )),
376
+ np .identity (n_inputs )],
377
+ [np .zeros ((n_inputs , n_states + 2 * n_inputs ))]])
378
+ expM = sp .linalg .expm (M )
379
+ Ad = expM [:n_states , :n_states ]
380
+ Bd1 = expM [:n_states , n_states + n_inputs :]
381
+ Bd0 = expM [:n_states , n_states :n_states + n_inputs ] - Bd1
382
+
383
+ for i in range (1 , n_steps ):
384
+ xout [:, i ] = (dot (Ad , xout [:, i - 1 ]) + dot (Bd0 , U [:, i - 1 ]) +
385
+ dot (Bd1 , U [:, i ]))
386
+ yout = dot (C , xout ) + dot (D , U )
374
387
375
388
yout = squeeze (yout )
376
- xout = xout . T
389
+ xout = squeeze ( xout )
377
390
378
391
else :
379
392
# Discrete time simulation using signal processing toolbox
0 commit comments