99from mujoco .mjx ._src .dataclasses import PyTreeNode
1010
1111from mpx .jax_ocp_solvers .jax_ocp_solvers import optimizers
12+ import mpx .utils .mpc_utils as mpc_utils
1213
1314
1415dir_path = os .path .dirname (os .path .realpath (__file__ ))
2122N = 12
2223mpc_frequency = 50.0
2324solver_mode = "equality"
24- equality_num_alpha = 5
25+ equality_num_alpha = 10
26+ regularization = jnp .array (1e-6 )
2527
2628timer_t = jnp .array ([0.5 , 0.0 , 0.0 , 0.5 ])
2729duty_factor = jnp .array ([0.65 ])
3032initial_height = 0.1
3133robot_height = 0.27
3234use_terrain_estimation = False
35+ clearance_speed = 0.2
3336
3437p0 = jnp .array ([0.0 , 0.0 , robot_height ])
3538quat0 = jnp .array ([1.0 , 0.0 , 0.0 , 0.0 ])
6568tau_slice = slice (nv , nv + n_joints )
6669grf_slice = slice (nv + n_joints , m )
6770
68- Qp = jnp .diag (jnp .array ([0.0 , 0.0 , 1.0e4 ]))
71+ Qp = jnp .diag (jnp .array ([0.0 , 0.0 , 1.0e6 ]))
6972Qrot = jnp .diag (jnp .array ([1.0e3 , 1.0e3 , 0.0 ]))
70- Qq = jnp .diag (jnp .ones (n_joints )) * 1.0e0
73+ Qq = jnp .diag (jnp .ones (n_joints )) * 1.0e2
7174Qdp = jnp .diag (jnp .array ([1.0 , 1.0 , 1.0 ])) * 5.0e3
7275Qomega = jnp .diag (jnp .array ([1.0 , 1.0 , 1.0 ])) * 1.0e2
73- Qdq = jnp .diag (jnp .ones (n_joints )) * 1.0e0
76+ Qdq = jnp .diag (jnp .ones (n_joints )) * 1.0e-1
77+ Qcontact = jnp .diag (jnp .tile (jnp .array ([1.0e5 , 1.0e5 , 1.0e5 ]), n_contact ))
7478Qacc = jnp .diag (jnp .ones (nv )) * 1.0e-1
75- Qtau = jnp .diag (jnp .ones (n_joints )) * 1.0e-3
76- Qgrf = jnp .diag (jnp .ones (3 * n_contact )) * 1.0e-5
77- W = jax .scipy .linalg .block_diag (Qp , Qrot , Qq , Qdp , Qomega , Qdq , Qacc , Qtau , Qgrf )
79+ Qtau = jnp .diag (jnp .ones (n_joints )* 1.0e-1 )
80+ Qgrf = jnp .diag (jnp .ones (3 * n_contact )) * 1.0e-2
81+ W = {
82+ "pos" : Qp ,
83+ "rot" : Qrot ,
84+ "q" : Qq ,
85+ "vel" : Qdp ,
86+ "omega" : Qomega ,
87+ "dq" : Qdq ,
88+ "contact" : Qcontact ,
89+ "acc" : Qacc ,
90+ "tau" : Qtau ,
91+ "grf" : Qgrf ,
92+ }
7893
7994initial_state = jnp .concatenate ([p0 , quat0 , q0 , jnp .zeros (nv )])
8095
@@ -120,13 +135,20 @@ def dynamics(x, u, t, parameter):
120135 qpos_next , qvel_next = _integrate_state (qpos , qvel , qacc )
121136 return jnp .concatenate ([qpos_next , qvel_next ])
122137
138+ @jax .jit
139+ def _foot_positions (qpos , qvel ):
140+ data = mjx .make_data (_MJX_MODEL )
141+ data = data .replace (qpos = qpos , qvel = qvel )
142+ data = mjx .fwd_position (_MJX_MODEL , data )
143+ return jnp .concatenate ([data .geom_xpos [contact_id ] for contact_id in _CONTACT_ID ])
144+
123145def equality (x , u , t , parameter ):
124- del t
146+
125147 qpos , qvel = _state_parts (x )
126148 qacc = u [qacc_slice ]
127149 tau = u [tau_slice ]
128150 grf = u [grf_slice ]
129- data = mjx .make_data (_MODEL )
151+ data = mjx .make_data (_MJX_MODEL )
130152 data = data .replace (qpos = qpos , qvel = qvel , qacc = qacc )
131153 data = mjx .fwd_position (_MJX_MODEL , data )
132154 data = mjx .fwd_velocity (_MJX_MODEL , data )
@@ -138,9 +160,11 @@ def equality(x, u, t, parameter):
138160 jac , _ = mjx .jac (_MJX_MODEL , data , data .geom_xpos [contact_id ], body_id )
139161 jacobians .append (jac )
140162 contact_jacobian = jnp .concatenate (jacobians , axis = 1 )
163+ contact = parameter [t , :4 ]
164+ grf = jnp .concatenate ([grf [:3 ]* contact [0 ], grf [3 :6 ]* contact [1 ], grf [6 :9 ]* contact [2 ], grf [9 :12 ]* contact [3 ]])
141165 contact_wrench = contact_jacobian @ grf
142166 generalized_actuation = jnp .concatenate ([jnp .zeros (6 , dtype = u .dtype ), tau ])
143- return qfrc_inverse + contact_wrench - generalized_actuation
167+ return qfrc_inverse - contact_wrench - generalized_actuation
144168
145169
146170def cost (W , reference , x , u , t ):
@@ -154,85 +178,43 @@ def cost(W, reference, x, u, t):
154178 acc = u [qacc_slice ]
155179 tau = u [tau_slice ]
156180 grf = u [grf_slice ]
157-
158- p_ref = reference [t , :3 ]
159- quat_ref = reference [t , 3 :7 ]
160- q_ref = reference [t , 7 : 7 + n_joints ]
161- dp_ref = reference [t , nq : nq + 3 ]
162- omega_ref = reference [t , nq + 3 : nq + 6 ]
163- dq_ref = reference [t , nq + 6 : nq + nv ]
181+ p_leg = _foot_positions (qpos , qvel )
182+ p_ref = reference ["p" ][t ]
183+ quat_ref = reference ["quat" ][t ]
184+ q_ref = reference ["q" ][t ]
185+ dp_ref = reference ["dp" ][t ]
186+ omega_ref = reference ["omega" ][t ]
187+ p_leg_ref = reference ["foot" ][t ]
188+ grf_ref = reference ["grf" ][t ]
164189
165190 quat_err = math .quat_sub (quat , quat_ref )
191+
166192 stage_cost = (
167- (p - p_ref ).T @ W [:3 , :3 ] @ (p - p_ref )
168- + quat_err .T @ W [3 :6 , 3 :6 ] @ quat_err
169- + (q - q_ref ).T @ W [6 : 6 + n_joints , 6 : 6 + n_joints ] @ (q - q_ref )
170- + (dp - dp_ref ).T
171- @ W [6 + n_joints : 9 + n_joints , 6 + n_joints : 9 + n_joints ]
172- @ (dp - dp_ref )
173- + (omega - omega_ref ).T
174- @ W [9 + n_joints : 12 + n_joints , 9 + n_joints : 12 + n_joints ]
175- @ (omega - omega_ref )
176- + (dq - dq_ref ).T
177- @ W [12 + n_joints : 12 + 2 * n_joints , 12 + n_joints : 12 + 2 * n_joints ]
178- @ (dq - dq_ref )
179- + acc .T @ W [nx_error : nx_error + nv , nx_error : nx_error + nv ] @ acc
180- + (tau ).T
181- @ W [nx_error + nv : nx_error + nv + n_joints , nx_error + nv : nx_error + nv + n_joints ]
182- @ (tau )
183- + (grf ).T
184- @ W [nx_error + nv + n_joints :, nx_error + nv + n_joints :]
185- @ (grf )
193+ (p - p_ref ).T @ W ["pos" ] @ (p - p_ref )
194+ + quat_err .T @ W ["rot" ] @ quat_err
195+ + (q - q_ref ).T @ W ["q" ] @ (q - q_ref )
196+ + (dp - dp_ref ).T @ W ["vel" ] @ (dp - dp_ref )
197+ + (omega - omega_ref ).T @ W ["omega" ] @ (omega - omega_ref )
198+ + dq .T @ W ["dq" ] @ dq
199+ + (p_leg - p_leg_ref ).T @ W ["contact" ] @ (p_leg - p_leg_ref )
200+ + acc .T @ W ["acc" ] @ acc
201+ + tau .T @ W ["tau" ] @ tau
202+ + (grf - grf_ref ).T @ W ["grf" ] @ (grf - grf_ref )
186203 )
187204 terminal_cost = (
188- (p - p_ref ).T @ W [:3 , :3 ] @ (p - p_ref )
189- + quat_err .T @ W [3 :6 , 3 :6 ] @ quat_err
190- + (q - q_ref ).T @ W [6 : 6 + n_joints , 6 : 6 + n_joints ] @ (q - q_ref )
191- + (dp - dp_ref ).T
192- @ W [6 + n_joints : 9 + n_joints , 6 + n_joints : 9 + n_joints ]
193- @ (dp - dp_ref )
194- + (omega - omega_ref ).T
195- @ W [9 + n_joints : 12 + n_joints , 9 + n_joints : 12 + n_joints ]
196- @ (omega - omega_ref )
197- + (dq - dq_ref ).T
198- @ W [12 + n_joints : 12 + 2 * n_joints , 12 + n_joints : 12 + 2 * n_joints ]
199- @ (dq - dq_ref )
205+ (p - p_ref ).T @ W ["pos" ] @ (p - p_ref )
206+ + quat_err .T @ W ["rot" ] @ quat_err
207+ + (q - q_ref ).T @ W ["q" ] @ (q - q_ref )
208+ + (dp - dp_ref ).T @ W ["vel" ] @ (dp - dp_ref )
209+ + (omega - omega_ref ).T @ W ["omega" ] @ (omega - omega_ref )
210+ + dq .T @ W ["dq" ] @ dq
200211 )
201212 return jnp .where (t == N , 0.5 * terminal_cost , 0.5 * stage_cost )
202213
203214
204- def hessian_approx (W , reference , x , u , t ):
205- del reference , x , u
206- state_diag = jnp .concatenate (
207- [
208- jnp .diag (W [:3 , :3 ]),
209- jnp .ones (4 ) * 1.0e-3 ,
210- jnp .diag (W [6 : 6 + n_joints , 6 : 6 + n_joints ]),
211- jnp .diag (W [6 + n_joints : 12 + 2 * n_joints , 6 + n_joints : 12 + 2 * n_joints ]),
212- ]
213- )
214- control_diag = jnp .concatenate (
215- [
216- jnp .diag (W [nx_error : nx_error + nv , nx_error : nx_error + nv ]),
217- jnp .diag (
218- W [
219- nx_error + nv : nx_error + nv + n_joints ,
220- nx_error + nv : nx_error + nv + n_joints ,
221- ]
222- ),
223- jnp .diag (W [nx_error + nv + n_joints :, nx_error + nv + n_joints :]),
224- ]
225- )
226- control_diag = jnp .where (t == N , jnp .ones (m ) * 1.0e-6 , control_diag )
227-
228- Q = jnp .diag (state_diag )
229- R = jnp .diag (control_diag )
230- M = jnp .zeros ((n , m ), dtype = W .dtype )
231- return Q , R , M
232-
233-
234215class InverseDynamicsMPCData (PyTreeNode ):
235216 dt : float
217+ time : jnp .ndarray
236218 duty_factor : jnp .ndarray
237219 step_freq : jnp .ndarray
238220 step_height : jnp .ndarray
@@ -243,6 +225,7 @@ class InverseDynamicsMPCData(PyTreeNode):
243225 V0 : jnp .ndarray
244226 Veq0 : jnp .ndarray
245227 W : jnp .ndarray
228+ regularization : jnp .ndarray
246229
247230
248231@partial (jax .jit , static_argnums = (0 , 1 ))
@@ -289,28 +272,43 @@ def __init__(self, config, limited_memory=False):
289272 self .model = mujoco .MjModel .from_xml_path (config .model_path )
290273 self .model .opt .timestep = config .dt
291274 self .mjx_model = mjx .put_model (self .model )
292-
275+ data = mujoco .MjData (self .model )
276+ mujoco .mj_fwdPosition (self .model , data )
293277 self .initial_state = jnp .asarray (config .initial_state )
294278 self .initial_X0 = jnp .tile (self .initial_state , (config .N + 1 , 1 ))
295279 self .initial_U0 = jnp .tile (config .u_ref , (config .N , 1 ))
296280 self .initial_V0 = jnp .zeros ((config .N + 1 , config .n ))
297281 self .initial_Veq0 = jnp .zeros ((config .N , config .equality_dim ))
282+ self .initial_liftoff = config .p_legs0
298283
299284 self .dynamics = config .dynamics
300285 solver = partial (
301286 optimizers .mpc_equality ,
302287 config .cost ,
303288 self .dynamics ,
304- config . hessian_approx ,
289+ None ,
305290 limited_memory ,
306291 equality = config .equality ,
307292 num_alpha = config .equality_num_alpha ,
308293 )
309294
310- def solve (reference , parameter , W , x0 , X0 , U0 , V0 , Veq0 ):
311- return solver (reference , parameter , W , x0 , X0 , U0 , V0 , Veq_in = Veq0 )
295+ def solve (reference , parameter , W , x0 , X0 , U0 , V0 , Veq0 , regularization ):
296+ return solver (
297+ reference ,
298+ parameter ,
299+ W ,
300+ x0 ,
301+ X0 ,
302+ U0 ,
303+ V0 ,
304+ Veq_in = Veq0 ,
305+ regularization = regularization ,
306+ )
312307
313308 self ._solve = jax .jit (solve )
309+ robot_mass = data .qM [0 ]
310+ self ._ref_gen = partial (config .reference_generator , mass = robot_mass )
311+ self ._timer_run = jax .jit (mpc_utils .timer_run )
314312 self ._update_warm_start = partial (
315313 _update_warm_start ,
316314 config .N ,
@@ -321,37 +319,50 @@ def solve(reference, parameter, W, x0, X0, U0, V0, Veq0):
321319 def make_data (self ):
322320 return InverseDynamicsMPCData (
323321 dt = dt ,
322+ time = jnp .asarray (0.0 , dtype = jnp .float32 ),
324323 duty_factor = duty_factor ,
325324 step_freq = step_freq ,
326325 step_height = step_height ,
327326 contact_time = timer_t ,
328- liftoff = jnp . zeros ( 3 * n_contact ) ,
327+ liftoff = self . initial_liftoff ,
329328 X0 = self .initial_X0 ,
330329 U0 = self .initial_U0 ,
331330 V0 = self .initial_V0 ,
332331 Veq0 = self .initial_Veq0 ,
333332 W = W ,
333+ regularization = regularization ,
334334 )
335335
336- def _reference (self , x , command ):
337- p_ref = x [:3 ] + jnp .array ([command [0 ], command [1 ], 0.0 ]) * (N * dt )
338- p_ref = p_ref .at [2 ].set (command [6 ])
339- qpos_ref = jnp .concatenate ([p_ref , x [3 :7 ], q0 ])
340- dp_ref = jnp .array ([command [0 ], command [1 ], 0.0 ])
341- omega_ref = jnp .array ([0.0 , 0.0 , command [5 ]])
342- dq_ref = jnp .zeros (n_joints )
343- x_ref = jnp .concatenate ([qpos_ref , dp_ref , omega_ref , dq_ref ])
344- return jnp .tile (x_ref , (N + 1 , 1 ))
345-
346336 def control_output (self , x0 , X , U , reference , parameter ):
347337 del x0 , X , reference , parameter
348338 return jnp .clip (U [0 , tau_slice ], min_torque , max_torque )
349339
350340 def run (self , data , x , command , contact = None ):
351- del contact
352- reference = self ._reference (x , command )
353- parameter = jnp .zeros ((N + 1 , 1 ), dtype = x .dtype )
354- X , U , V , Veq = self ._solve (
341+ if contact is None :
342+ contact = jnp .zeros (n_contact , dtype = x .dtype )
343+ current_time = data .time + jnp .asarray (1 / self .mpc_frequency , dtype = data .time .dtype )
344+ _ , contact_time = self ._timer_run (
345+ data .duty_factor ,
346+ data .step_freq ,
347+ data .contact_time ,
348+ 1 / self .mpc_frequency ,
349+ )
350+ qpos , qvel = _state_parts (x )
351+ foot = _foot_positions (qpos , qvel )
352+ reference_state = jnp .concatenate ([x , foot , jnp .zeros (3 * n_contact , dtype = x .dtype )])
353+ reference , parameter , liftoff = self ._ref_gen (
354+ duty_factor = data .duty_factor ,
355+ step_freq = data .step_freq ,
356+ step_height = data .step_height ,
357+ t_timer = data .contact_time ,
358+ x = reference_state ,
359+ foot = foot ,
360+ input = command ,
361+ liftoff = data .liftoff ,
362+ contact = contact ,
363+ current_time = current_time ,
364+ )
365+ X , U , V , Veq , regularization , alpha_best , any_accepted = self ._solve (
355366 reference ,
356367 parameter ,
357368 data .W ,
@@ -360,6 +371,7 @@ def run(self, data, x, command, contact=None):
360371 data .U0 ,
361372 data .V0 ,
362373 data .Veq0 ,
374+ data .regularization ,
363375 )
364376 valid_solution = jnp .logical_not (jnp .isnan (U [0 , 0 ]))
365377 tau = jax .lax .cond (
@@ -377,23 +389,54 @@ def run(self, data, x, command, contact=None):
377389 V ,
378390 Veq ,
379391 )
380- return data .replace (X0 = X0 , U0 = U0 , V0 = V0 , Veq0 = Veq0 ), tau , q , dq
392+ return (
393+ data .replace (
394+ time = current_time ,
395+ X0 = X0 ,
396+ U0 = U0 ,
397+ V0 = V0 ,
398+ Veq0 = Veq0 ,
399+ contact_time = contact_time ,
400+ liftoff = liftoff ,
401+ regularization = regularization ,
402+ ),
403+ tau ,
404+ q ,
405+ dq ,
406+ alpha_best ,
407+ any_accepted ,
408+ reference ,
409+ )
381410
382411 def reset (self , data , qpos , qvel , foot = None ):
383- del foot
384412 x = (
385413 self .initial_state .at [self .qpos_slice ].set (jnp .ravel (qpos ))
386414 .at [self .qvel_slice ].set (jnp .ravel (qvel ))
387415 )
416+ liftoff = _foot_positions (jnp .ravel (qpos ), jnp .ravel (qvel )) if foot is None else jnp .ravel (foot )
388417 return data .replace (
418+ time = jnp .asarray (0.0 , dtype = jnp .float32 ),
389419 X0 = jnp .tile (x , (N + 1 , 1 )),
390420 U0 = self .initial_U0 ,
391421 V0 = self .initial_V0 ,
392422 Veq0 = self .initial_Veq0 ,
393- liftoff = jnp . zeros ( 3 * n_contact ) ,
423+ liftoff = liftoff ,
394424 contact_time = timer_t ,
395425 )
396426
397427
398428def state_to_qpos (x ):
399429 return x [:nq ]
430+
431+
432+ reference_generator = partial (
433+ mpc_utils .reference_generator ,
434+ use_terrain_estimation ,
435+ N ,
436+ dt ,
437+ n_joints ,
438+ n_contact ,
439+ foot0 = p_legs0 ,
440+ q0 = q0 ,
441+ clearence_speed = clearance_speed ,
442+ )
0 commit comments