@@ -155,6 +155,8 @@ def __init__(self,
155
155
if forward is not None : self .forward = forward
156
156
if reverse is not None : self .reverse = reverse
157
157
158
+ # Save the length of the flat flag
159
+
158
160
def forward (self , x , u , params = {}):
159
161
"""Compute the flat flag given the states and input.
160
162
@@ -217,10 +219,33 @@ def _flat_outfcn(self, t, x, u, params={}):
217
219
return np .array (zflag [:][0 ])
218
220
219
221
222
+ # Utility function to compute flag matrix given a basis
223
+ def _basis_flag_matrix (sys , basis , flag , t , params = {}):
224
+ """Compute the matrix of basis functions and their derivatives
225
+
226
+ This function computes the matrix ``M`` that is used to solve for the
227
+ coefficients of the basis functions given the state and input. Each
228
+ column of the matrix corresponds to a basis function and each row is a
229
+ derivative, with the derivatives (flag) for each output stacked on top
230
+ of each other.
231
+
232
+ """
233
+ flagshape = [len (f ) for f in flag ]
234
+ M = np .zeros ((sum (flagshape ), basis .N * sys .ninputs ))
235
+ flag_off = 0
236
+ coeff_off = 0
237
+ for i , flag_len in enumerate (flagshape ):
238
+ for j , k in itertools .product (range (basis .N ), range (flag_len )):
239
+ M [flag_off + k , coeff_off + j ] = basis .eval_deriv (j , k , t )
240
+ flag_off += flag_len
241
+ coeff_off += basis .N
242
+ return M
243
+
244
+
220
245
# Solve a point to point trajectory generation problem for a flat system
221
246
def point_to_point (
222
247
sys , timepts , x0 = 0 , u0 = 0 , xf = 0 , uf = 0 , T0 = 0 , basis = None , cost = None ,
223
- constraints = None , initial_guess = None , minimize_kwargs = {}):
248
+ constraints = None , initial_guess = None , minimize_kwargs = {}, ** kwargs ):
224
249
"""Compute trajectory between an initial and final conditions.
225
250
226
251
Compute a feasible trajectory for a differentially flat system between an
@@ -251,9 +276,9 @@ def point_to_point(
251
276
252
277
basis : :class:`~control.flatsys.BasisFamily` object, optional
253
278
The basis functions to use for generating the trajectory. If not
254
- specified, the :class:`~control.flatsys.PolyFamily` basis family will be
255
- used, with the minimal number of elements required to find a feasible
256
- trajectory (twice the number of system states)
279
+ specified, the :class:`~control.flatsys.PolyFamily` basis family
280
+ will be used, with the minimal number of elements required to find a
281
+ feasible trajectory (twice the number of system states)
257
282
258
283
cost : callable
259
284
Function that returns the integral cost given the current state
@@ -287,6 +312,12 @@ def point_to_point(
287
312
`eval()` function, we can be used to compute the value of the state
288
313
and input and a given time t.
289
314
315
+ Notes
316
+ -----
317
+ Additional keyword parameters can be used to fine tune the behavior of
318
+ the underlying optimization function. See `minimize_*` keywords in
319
+ :func:`OptimalControlProblem` for more information.
320
+
290
321
"""
291
322
#
292
323
# Make sure the problem is one that we can handle
@@ -296,7 +327,7 @@ def point_to_point(
296
327
u0 = _check_convert_array (u0 , [(sys .ninputs ,), (sys .ninputs , 1 )],
297
328
'Initial input: ' , squeeze = True )
298
329
xf = _check_convert_array (xf , [(sys .nstates ,), (sys .nstates , 1 )],
299
- 'Final state: ' , squeeze = True )
330
+ 'Final state: ' , squeeze = True )
300
331
uf = _check_convert_array (uf , [(sys .ninputs ,), (sys .ninputs , 1 )],
301
332
'Final input: ' , squeeze = True )
302
333
@@ -305,6 +336,12 @@ def point_to_point(
305
336
Tf = timepts [- 1 ]
306
337
T0 = timepts [0 ] if len (timepts ) > 1 else T0
307
338
339
+ # Process keyword arguments
340
+ minimize_kwargs ['method' ] = kwargs .pop ('minimize_method' , None )
341
+ minimize_kwargs ['options' ] = kwargs .pop ('minimize_options' , {})
342
+ if kwargs :
343
+ raise TypeError ("unrecognized keywords: " , str (kwargs ))
344
+
308
345
#
309
346
# Determine the basis function set to use and make sure it is big enough
310
347
#
@@ -328,8 +365,7 @@ def point_to_point(
328
365
# We need to compute the output "flag": [z(t), z'(t), z''(t), ...]
329
366
# and then evaluate this at the initial and final condition.
330
367
#
331
- # TODO: should be able to represent flag variables as 1D arrays
332
- # TODO: need inputs to fully define the flag
368
+
333
369
zflag_T0 = sys .forward (x0 , u0 )
334
370
zflag_Tf = sys .forward (xf , uf )
335
371
@@ -340,41 +376,13 @@ def point_to_point(
340
376
# essentially amounts to evaluating the basis functions and their
341
377
# derivatives at the initial and final conditions.
342
378
343
- # Figure out the size of the problem we are solving
344
- flag_tot = np .sum ([len (zflag_T0 [i ]) for i in range (sys .ninputs )])
379
+ # Compute the flags for the initial and final states
380
+ M_T0 = _basis_flag_matrix (sys , basis , zflag_T0 , T0 )
381
+ M_Tf = _basis_flag_matrix (sys , basis , zflag_Tf , Tf )
345
382
346
- # Start by creating an empty matrix that we can fill up
347
- # TODO: allow a different number of basis elements for each flat output
348
- M = np .zeros ((2 * flag_tot , basis .N * sys .ninputs ))
349
-
350
- # Now fill in the rows for the initial and final states
351
- # TODO: vectorize
352
- flag_off = 0
353
- coeff_off = 0
354
-
355
- for i in range (sys .ninputs ):
356
- flag_len = len (zflag_T0 [i ])
357
- for j in range (basis .N ):
358
- for k in range (flag_len ):
359
- M [flag_off + k , coeff_off + j ] = basis .eval_deriv (j , k , T0 )
360
- M [flag_tot + flag_off + k , coeff_off + j ] = \
361
- basis .eval_deriv (j , k , Tf )
362
- flag_off += flag_len
363
- coeff_off += basis .N
364
-
365
- # Create an empty matrix that we can fill up
366
- Z = np .zeros (2 * flag_tot )
367
-
368
- # Compute the flag vector to use for the right hand side by
369
- # stacking up the flags for each input
370
- # TODO: make this more pythonic
371
- flag_off = 0
372
- for i in range (sys .ninputs ):
373
- flag_len = len (zflag_T0 [i ])
374
- for j in range (flag_len ):
375
- Z [flag_off + j ] = zflag_T0 [i ][j ]
376
- Z [flag_tot + flag_off + j ] = zflag_Tf [i ][j ]
377
- flag_off += flag_len
383
+ # Stack the initial and final matrix/flag for the point to point problem
384
+ M = np .vstack ([M_T0 , M_Tf ])
385
+ Z = np .hstack ([np .hstack (zflag_T0 ), np .hstack (zflag_Tf )])
378
386
379
387
#
380
388
# Solve for the coefficients of the flat outputs
@@ -404,17 +412,7 @@ def traj_cost(null_coeffs):
404
412
# Evaluate the costs at the listed time points
405
413
costval = 0
406
414
for t in timepts :
407
- M_t = np .zeros ((flag_tot , basis .N * sys .ninputs ))
408
- flag_off = 0
409
- coeff_off = 0
410
- for i in range (sys .ninputs ):
411
- flag_len = len (zflag_T0 [i ])
412
- for j , k in itertools .product (
413
- range (basis .N ), range (flag_len )):
414
- M_t [flag_off + k , coeff_off + j ] = \
415
- basis .eval_deriv (j , k , t )
416
- flag_off += flag_len
417
- coeff_off += basis .N
415
+ M_t = _basis_flag_matrix (sys , basis , zflag_T0 , t )
418
416
419
417
# Compute flag at this time point
420
418
zflag = (M_t @ coeffs ).reshape (sys .ninputs , - 1 )
@@ -452,17 +450,7 @@ def traj_const(null_coeffs):
452
450
values = []
453
451
for i , t in enumerate (timepts ):
454
452
# Calculate the states and inputs for the flat output
455
- M_t = np .zeros ((flag_tot , basis .N * sys .ninputs ))
456
- flag_off = 0
457
- coeff_off = 0
458
- for i in range (sys .ninputs ):
459
- flag_len = len (zflag_T0 [i ])
460
- for j , k in itertools .product (
461
- range (basis .N ), range (flag_len )):
462
- M_t [flag_off + k , coeff_off + j ] = \
463
- basis .eval_deriv (j , k , t )
464
- flag_off += flag_len
465
- coeff_off += basis .N
453
+ M_t = _basis_flag_matrix (sys , basis , zflag_T0 , t )
466
454
467
455
# Compute flag at this time point
468
456
zflag = (M_t @ coeffs ).reshape (sys .ninputs , - 1 )
@@ -501,7 +489,7 @@ def traj_const(null_coeffs):
501
489
502
490
# Process the initial condition
503
491
if initial_guess is None :
504
- initial_guess = np .zeros (basis . N * sys . ninputs - 2 * flag_tot )
492
+ initial_guess = np .zeros (M . shape [ 1 ] - M . shape [ 0 ] )
505
493
else :
506
494
raise NotImplementedError ("Initial guess not yet implemented." )
507
495
@@ -514,7 +502,7 @@ def traj_const(null_coeffs):
514
502
else :
515
503
raise RuntimeError (
516
504
"Unable to solve optimal control problem\n " +
517
- "scipy.optimize.minimize returned " + res .message )
505
+ "scipy.optimize.minimize returned " + res .message )
518
506
519
507
#
520
508
# Transform the trajectory from flat outputs to states and inputs
0 commit comments