@@ -48,7 +48,7 @@ def factory(
48
48
params_syms = sym_exps ["params_syms" ]
49
49
50
50
@jit
51
- def select_params_for_lambdify (params : Dict [str , Array ]) -> List [Array ]:
51
+ def select_params_for_lambdify_fn (params : Dict [str , Array ]) -> List [Array ]:
52
52
"""
53
53
Select the parameters for lambdify
54
54
Args:
@@ -101,6 +101,27 @@ def select_params_for_lambdify(params: Dict[str, Array]) -> List[Array]:
101
101
"jax" ,
102
102
)
103
103
chi_lambda_sms .append (chi_lambda )
104
+ J_lambda_sms = []
105
+ for J_exp in sym_exps ["exps" ]["J_sms" ]:
106
+ J_lambda = sp .lambdify (
107
+ params_syms_cat
108
+ + sym_exps ["state_syms" ]["xi" ]
109
+ + [sym_exps ["state_syms" ]["s" ]],
110
+ J_exp ,
111
+ "jax" ,
112
+ )
113
+ J_lambda_sms .append (J_lambda )
114
+ J_d_lambda_sms = []
115
+ for J_d_exp in sym_exps ["exps" ]["J_d_sms" ]:
116
+ J_d_lambda = sp .lambdify (
117
+ params_syms_cat
118
+ + sym_exps ["state_syms" ]["xi" ]
119
+ + sym_exps ["state_syms" ]["xi_d" ]
120
+ + [sym_exps ["state_syms" ]["s" ]],
121
+ J_d_exp ,
122
+ "jax" ,
123
+ )
124
+ J_d_lambda_sms .append (J_d_lambda )
104
125
105
126
B_lambda = sp .lambdify (
106
127
params_syms_cat + sym_exps ["state_syms" ]["xi" ], sym_exps ["exps" ]["B" ], "jax"
@@ -153,6 +174,30 @@ def apply_eps_to_bend_strains(xi: Array, _eps: float) -> Array:
153
174
154
175
return xi_epsed
155
176
177
+ def classify_segment (params : Dict [str , Array ], s : Array ) -> Tuple [Array , Array ] :
178
+ """
179
+ Classify the point along the robot to the corresponding segment
180
+ Args:
181
+ params: Dictionary of robot parameters
182
+ s: point coordinate along the robot in the interval [0, L].
183
+ Returns:
184
+ segment_idx: index of the segment
185
+ s_segment: point coordinate along the segment in the interval [0, l_segment
186
+ """
187
+ # cumsum of the segment lengths
188
+ l_cum = jnp .cumsum (params ["l" ])
189
+ # add zero to the beginning of the array
190
+ l_cum_padded = jnp .concatenate ([jnp .array ([0.0 ]), l_cum ], axis = 0 )
191
+ # determine in which segment the point is located
192
+ # use argmax to find the last index where the condition is true
193
+ segment_idx = (
194
+ l_cum .shape [0 ] - 1 - jnp .argmax ((s >= l_cum_padded [:- 1 ])[::- 1 ]).astype (int )
195
+ )
196
+ # point coordinate along the segment in the interval [0, l_segment]
197
+ s_segment = s - l_cum_padded [segment_idx ]
198
+
199
+ return segment_idx , s_segment
200
+
156
201
@jit
157
202
def forward_kinematics_fn (
158
203
params : Dict [str , Array ], q : Array , s : Array , eps : float = global_eps
@@ -172,31 +217,57 @@ def forward_kinematics_fn(
172
217
"""
173
218
# map the configuration to the strains
174
219
xi = xi_eq + B_xi @ q
175
-
176
220
# add a small number to the bending strain to avoid singularities
177
221
xi_epsed = apply_eps_to_bend_strains (xi , eps )
178
222
179
- # cumsum of the segment lengths
180
- l_cum = jnp .cumsum (params ["l" ])
181
- # add zero to the beginning of the array
182
- l_cum_padded = jnp .concatenate ([jnp .array ([0.0 ]), l_cum ], axis = 0 )
183
- # determine in which segment the point is located
184
- # use argmax to find the last index where the condition is true
185
- segment_idx = (
186
- l_cum .shape [0 ] - 1 - jnp .argmax ((s >= l_cum_padded [:- 1 ])[::- 1 ]).astype (int )
187
- )
188
- # point coordinate along the segment in the interval [0, l_segment]
189
- s_segment = s - l_cum_padded [segment_idx ]
223
+ # classify the point along the robot to the corresponding segment
224
+ segment_idx , s_segment = classify_segment (params , s )
190
225
191
226
# convert the dictionary of parameters to a list, which we can pass to the lambda function
192
- params_for_lambdify = select_params_for_lambdify (params )
227
+ params_for_lambdify = select_params_for_lambdify_fn (params )
193
228
194
229
chi = lax .switch (
195
230
segment_idx , chi_lambda_sms , * params_for_lambdify , * xi_epsed , s_segment
196
231
).squeeze ()
197
232
198
233
return chi
199
234
235
+ @jit
236
+ def jacobian_fn (
237
+ params : Dict [str , Array ], q : Array , s : Array , eps : float = global_eps
238
+ ) -> Array :
239
+ """
240
+ Evaluate the forward kinematics the tip of the links
241
+ Args:
242
+ params: Dictionary of robot parameters
243
+ q: generalized coordinates of shape (n_q, )
244
+ s: point coordinate along the rod in the interval [0, L].
245
+ eps: small number to avoid singularities (e.g., division by zero)
246
+ Returns:
247
+ J: Jacobian matrix of shape (3, n_q) of the backbone point in Cartesian-space
248
+ Relates the configuration-space velocity q_d to the Cartesian-space velocity chi_d,
249
+ where chi_d = J @ q_d. Chi_d consists of [p_x_d, p_y_d, theta_d]
250
+ """
251
+ # map the configuration to the strains
252
+ xi = xi_eq + B_xi @ q
253
+ # add a small number to the bending strain to avoid singularities
254
+ xi_epsed = apply_eps_to_bend_strains (xi , eps )
255
+
256
+ # classify the point along the robot to the corresponding segment
257
+ segment_idx , s_segment = classify_segment (params , s )
258
+
259
+ # convert the dictionary of parameters to a list, which we can pass to the lambda function
260
+ params_for_lambdify = select_params_for_lambdify_fn (params )
261
+
262
+ J = lax .switch (
263
+ segment_idx , J_lambda_sms , * params_for_lambdify , * xi_epsed , s_segment
264
+ ).squeeze ()
265
+
266
+ # apply the strain basis to the Jacobian
267
+ J = J @ B_xi
268
+
269
+ return J
270
+
200
271
@jit
201
272
def dynamical_matrices_fn (
202
273
params : Dict [str , Array ], q : Array , q_d : Array , eps : float = 1e4 * global_eps
@@ -239,7 +310,7 @@ def dynamical_matrices_fn(
239
310
# dissipative matrix from the parameters
240
311
D = params .get ("D" , jnp .zeros ((n_xi , n_xi )))
241
312
242
- params_for_lambdify = select_params_for_lambdify (params )
313
+ params_for_lambdify = select_params_for_lambdify_fn (params )
243
314
244
315
B = B_xi .T @ B_lambda (* params_for_lambdify , * xi_epsed ) @ B_xi
245
316
C_xi = C_lambda (* params_for_lambdify , * xi_epsed , * xi_d )
@@ -303,7 +374,7 @@ def potential_energy_fn(params: Dict[str, Array], q: Array, eps: float = 1e4 * g
303
374
U_K = (xi - xi_eq ).T @ K @ (xi - xi_eq ) # evaluate K(xi) = K @ xi
304
375
305
376
# gravitational potential energy
306
- params_for_lambdify = select_params_for_lambdify (params )
377
+ params_for_lambdify = select_params_for_lambdify_fn (params )
307
378
U_G = U_lambda (* params_for_lambdify , * xi_epsed )
308
379
309
380
# total potential energy
@@ -327,11 +398,85 @@ def energy_fn(params: Dict[str, Array], q: Array, q_d: Array) -> Array:
327
398
328
399
return E
329
400
401
+ def operational_space_dynamical_matrices_fn (
402
+ params : Dict [str , Array ],
403
+ q : Array ,
404
+ q_d : Array ,
405
+ s : Array ,
406
+ B : Array ,
407
+ C : Array ,
408
+ eps : float = 1e4 * global_eps ,
409
+ ) -> Tuple [Array , Array , Array , Array , Array ]:
410
+ """
411
+ Compute the dynamics in operational space.
412
+ The implementation is based on Chapter 7.8 of "Modelling, Planning and Control of Robotics" by
413
+ Siciliano, Sciavicco, Villani, Oriolo.
414
+ Args:
415
+ params: dictionary of parameters
416
+ q: generalized coordinates of shape (n_q,)
417
+ q_d: generalized velocities of shape (n_q,)
418
+ s: point coordinate along the robot in the interval [0, L].
419
+ B: inertia matrix in the generalized coordinates of shape (n_q, n_q)
420
+ C: coriolis matrix derived with Christoffer symbols in the generalized coordinates of shape (n_q, n_q)
421
+ eps: small number to avoid singularities (e.g., division by zero)
422
+ Returns:
423
+ Lambda: inertia matrix in the operational space of shape (3, 3)
424
+ mu: matrix with corioli and centrifugal terms in the operational space of shape (3, 3)
425
+ J: Jacobian of the Cartesian pose with respect to the generalized coordinates of shape (3, n_q)
426
+ J: time-derivative of the Jacobian of the end-effector pose with respect to the generalized coordinates
427
+ of shape (3, n_q)
428
+ JB_pinv: Dynamically-consistent pseudo-inverse of the Jacobian. Allows the mapping of torques
429
+ from the generalized coordinates to the operational space: f = JB_pinv.T @ tau_q
430
+ Shape (n_q, 3)
431
+ """
432
+ ## map the configuration to the strains
433
+ xi = xi_eq + B_xi @ q
434
+ xi_d = B_xi @ q_d
435
+ # add a small number to the bending strain to avoid singularities
436
+ xi_epsed = apply_eps_to_bend_strains (xi , eps )
437
+
438
+ # classify the point along the robot to the corresponding segment
439
+ segment_idx , s_segment = classify_segment (params , s )
440
+
441
+ # convert the dictionary of parameters to a list, which we can pass to the lambda function
442
+ params_for_lambdify = select_params_for_lambdify_fn (params )
443
+
444
+ # Jacobian and its time-derivative
445
+ J = lax .switch (
446
+ segment_idx , J_lambda_sms , * params_for_lambdify , * xi_epsed , s_segment
447
+ ).squeeze ()
448
+ J_d = lax .switch (
449
+ segment_idx , J_d_lambda_sms , * params_for_lambdify , * xi_epsed , * xi_d , s_segment
450
+ ).squeeze ()
451
+ # apply the strain basis to the J and J_d
452
+ J = J @ B_xi
453
+ J_d = J_d @ B_xi
454
+
455
+ # inverse of the inertia matrix in the configuration space
456
+ B_inv = jnp .linalg .inv (B )
457
+
458
+ Lambda = jnp .linalg .inv (
459
+ J @ B_inv @ J .T
460
+ ) # inertia matrix in the operational space
461
+ mu = Lambda @ (
462
+ J @ B_inv @ C - J_d
463
+ ) # coriolis and centrifugal matrix in the operational space
464
+
465
+ JB_pinv = (
466
+ B_inv @ J .T @ Lambda
467
+ ) # dynamically-consistent pseudo-inverse of the Jacobian
468
+ # apply TODO: remove
469
+ # JB_pinv = B_xi.T @ JB_pinv
470
+
471
+ return Lambda , mu , J , J_d , JB_pinv
472
+
330
473
auxiliary_fns = {
331
474
"apply_eps_to_bend_strains" : apply_eps_to_bend_strains ,
475
+ "jacobian_fn" : jacobian_fn ,
332
476
"kinetic_energy_fn" : kinetic_energy_fn ,
333
477
"potential_energy_fn" : potential_energy_fn ,
334
478
"energy_fn" : energy_fn ,
479
+ "operational_space_dynamical_matrices_fn" : operational_space_dynamical_matrices_fn ,
335
480
}
336
481
337
482
return B_xi , forward_kinematics_fn , dynamical_matrices_fn , auxiliary_fns
0 commit comments