11import os
22from functools import partial
3+ import time
34
45import jax
56import jax .numpy as jnp
67
8+ from mpx .utils import mpc_utils
79import mpx .utils .models as mpc_dyn_model
810import mpx .utils .objectives as mpc_objectives
911
1214model_path = os .path .abspath (os .path .join (dir_path , ".." )) + "/data/boston_dynamics_spot/scene_arm.xml"
1315
1416# Contact frame names and body names for the Spot feet / lower legs.
15- contact_frame = ["FL" , "FR" , "HL" , "HR" ]
16- body_name = ["fl_lleg" , "fr_lleg" , "hl_lleg" , "hr_lleg" ]
17- # contact_frame = ["FL", "FR", "RL", "RR"]
18- # body_name = ["FL_calf", "FR_calf", "RL_calf", "RR_calf"]
17+ contact_frame = ["FL" , "FR" , "HL" , "HR" ,"arm" ]
18+ body_name = ["fl_lleg" , "fr_lleg" , "hl_lleg" , "hr_lleg" ,"arm_link_fngr" ]
1919
2020# Time and stage parameters.
2121dt = 0.02
3131# robot_height = 0.40
3232initial_height = 0.46
3333robot_height = 0.46
34+ clearance_speed = 0.2
3435
3536# Initial base state and nominal joint posture.
3637p0 = jnp .array ([0.0 , 0.0 , initial_height ])
4546 - 0.34 , 0.175 , 0.0 ,
4647 - 0.34 , - 0.175 , 0.0 ,
4748])
48- # p_legs0 = jnp.array([
49- # 0.26635823, 0.1658, 0.03254209,
50- # 0.26635823, -0.1658, 0.03254209,
51- # -0.32934177, 0.1658, 0.03254209,
52- # -0.32934177, -0.1658, 0.03254209])
5349
5450# Dimensions.
5551n_joints = 12 + 7
56- n_contact = len (contact_frame )
57- n = 13 + 2 * n_joints + 6 * n_contact
52+ n_leg = len (contact_frame )
53+ n_contact = n_leg - 1 # Exclude the arm contact from the contact cost.
54+ n = 13 + 2 * n_joints + 3 * n_contact + 3 * n_leg
5855m = n_joints
59- grf_as_state = True
6056
57+ foot_slice = slice (13 + 2 * n_joints , 13 + 2 * n_joints + 3 * (n_contact ))
58+ leg_slice = slice (13 + 2 * n_joints , 13 + 2 * n_joints + 3 * (n_leg ))
6159# Reference controls.
6260u_ref = jnp .zeros (m )
6361
64- # Cost weights.
65- # Qp = jnp.diag(jnp.array([0.0, 0.0, 1e4]))
66- # Qrot = jnp.diag(jnp.array([1000.0, 1000.0, 0.0])) * 10
67- # # Qq = jnp.diag(jnp.ones(n_joints)) * 1e0
68- # Qq = jnp.diag(jnp.concatenate([jnp.ones(7) * 1e3, jnp.ones(12) * 1e-1]))
69- # Qdp = jnp.diag(jnp.array([1.0, 1.0, 1.0])) * 1e3
70- # Qomega = jnp.diag(jnp.array([1.0, 1.0, 10.0])) * 1e2
71- # Qdq = jnp.diag(jnp.ones(n_joints)) * 1e-1
72- # Qtau = jnp.diag(jnp.ones(n_joints)) * 1e-2
73- # Q_grf = jnp.diag(jnp.ones(3 * n_contact)) * 1e-2
74- # Qleg = jnp.diag(jnp.tile(jnp.array([1e4, 1e4, 1e5]), n_contact))
75-
76- # Values for go2
62+ # Values for spot
7763Qp = jnp .diag (jnp .array ([0 , 0 , 1e4 ])) # Cost matrix for position
78- Qrot = jnp .diag (jnp .array ([1000 , 1000 , 0 ])) # Cost matrix for rotation
79- Qq = jnp .diag (jnp .concatenate ([jnp .ones (7 ) * 5e2 , jnp .ones (12 ) * 1e-1 ])) # Cost matrix for joint angles
80- Qdp = jnp .diag (jnp .array ([1 , 1 , 1 ])) * 5e3 # Cost matrix for position derivatives
81- Qomega = jnp .diag (jnp .array ([1 , 1 , 1 ])) * 1e2 # Cost matrix for angular velocity
82- Qdq = jnp .diag (jnp .ones (n_joints )) * 1e-1 # Cost matrix for joint angle derivatives
64+ Qrot = jnp .diag (jnp .array ([100 , 100 , 0 ])) # Cost matrix for rotation
65+ Qq = jnp .diag (jnp .concatenate ([jnp .ones (7 ) * 5e0 , jnp .ones (12 ) * 1e-1 ])) # Cost matrix for joint angles
66+ Qdp = jnp .diag (jnp .array ([5 , 5 , 1 ])) * 1e1 # Cost matrix for position derivatives
67+ Qomega = jnp .diag (jnp .array ([1 , 1 , 1 ])) * 1e1 # Cost matrix for angular velocity
68+ Qdq = jnp .diag (jnp .ones (n_joints )) * 1e0 # Cost matrix for joint angle derivatives
8369Qtau = jnp .diag (jnp .ones (n_joints )) * 1e-2 # Cost matrix for torques
8470Q_grf = jnp .diag (jnp .ones (3 * n_contact )) * 1e-3 # Cost matrix for ground reaction forces
8571
8672# For the leg contact cost, repeat the unit cost for each contact point.
87- Qleg = jnp .diag (jnp .tile (jnp .array ([1e4 ,1e4 ,1e5 ]),n_contact ))
73+ weight_leg = jnp .tile (jnp .array ([1e3 ,1e3 ,1e5 ]),n_contact )
74+ weight_arm = jnp .array ([5e4 ,5e4 ,5e4 ])
75+ Qcontact = jnp .diag (jnp .concatenate ([weight_leg , weight_arm ]))
8876
89- W = jax .scipy .linalg .block_diag (Qp , Qrot , Qq , Qdp , Qomega , Qdq , Qleg , Qtau , Q_grf )
77+ W = {"pos" : Qp , "rot" : Qrot , "q" : Qq , "vel" : Qdp , "omega" : Qomega , "dq" : Qdq , "tau" : Qtau , "contact" : Qcontact , "grf" : Q_grf }
78+ # jax.scipy.linalg.block_diag(Qp, Qrot, Qq, Qdp, Qomega, Qdq, Qcontact, Qtau, Q_grf)
9079
9180use_terrain_estimation = False
92- _state_extra = n - (13 + 2 * n_joints + 3 * n_contact )
9381initial_state = jnp .concatenate (
94- [p0 , quat0 , q0 , jnp .zeros (6 + n_joints ), p_legs0 , jnp .zeros (_state_extra )]
82+ [p0 , quat0 , q0 , jnp .zeros (6 + n_joints ), p_legs0 , jnp .zeros (3 + 3 * n_contact )]
9583)
9684
97- cost = partial (mpc_objectives .quadruped_wb_obj , True , n_joints , n_contact , N )
85+ cost = partial (mpc_objectives .quadruped_wb_obj , True , n_joints , n_contact , n_leg , N )
9886hessian_approx = None
9987
10088def dynamics (model , mjx_model , contact_id , body_id ):
@@ -109,32 +97,58 @@ def dynamics(model, mjx_model, contact_id, body_id):
10997 )
11098
11199# Torque bounds used by the MPC cost / clipping.
112- max_torque = 500
113- min_torque = - 500
100+ max_torque = 300
101+ min_torque = - 300
114102solver_mode = "primal_dual" # Solver mode for the optimization problem
115103
116- extra_qref_data = {
117- "amp" : jnp .array ([2.0 , 0.5 , 0.4 , 0.6 , 0.7 , 0.8 , 0.5 ]),
118- "freq" : jnp .array ([0.2 , 1.0 , 1.2 , 0.8 , 0.9 , 1.0 , 0.5 ]),
119- "joint_index" : jnp .arange (7 , dtype = jnp .int32 ),
120- }
121-
122- def extra_qref_fn (q_ref , current_time , data ):
123- if data is None :
124- return q_ref
125-
126- arm_amp_ref = data ["amp" ]
127- arm_freq_ref = data ["freq" ]
128- arm_joint_index = data ["joint_index" ]
104+ def extra_ref_fun (reference ,current_time ):
129105
130106 def arm_fn (t , carry ):
131- q_ref = carry
107+ arm_pos_ref = carry
132108 #
133109 time_n = t * dt + current_time
134- arm_pos = arm_amp_ref * jnp .sin (2 * jnp .pi * arm_freq_ref * time_n ) + q0 [arm_joint_index ]
135-
136- q_ref = q_ref .at [t ,arm_joint_index ].set (arm_pos )
137- return (q_ref )
138- init_carry = q_ref
139- q_ref = jax .lax .fori_loop (0 , N + 1 , arm_fn , init_carry )
140- return q_ref
110+ arm_pos = jnp .array ([0.75 + 0.2 * time_n ,0.2 * jnp .sin (2 * jnp .pi * 0.25 * time_n ), 0.5 + 0.2 * jnp .cos (2 * jnp .pi * 0.25 * time_n )])
111+
112+ arm_pos_ref = arm_pos_ref .at [t ].set (arm_pos )
113+ return (arm_pos_ref )
114+ init_carry = jnp .zeros ((N + 1 , 3 ))
115+ arm_pos_ref = jax .lax .fori_loop (0 , N + 1 , arm_fn , init_carry )
116+ reference ['foot' ] = jnp .concatenate ([reference ['foot' ], arm_pos_ref ], axis = 1 )
117+ return reference
118+
119+ reference_generator = partial (
120+ mpc_utils .reference_generator ,
121+ use_terrain_estimation ,
122+ N ,
123+ dt ,
124+ n_joints ,
125+ n_contact ,
126+ foot0 = p_legs0 ,
127+ q0 = q0 ,
128+ clearence_speed = clearance_speed ,
129+ extra_ref_fun = extra_ref_fun
130+ )
131+
132+ import mpx .utils .mpc_wrapper as mpc_wrapper
133+
134+ class MpcWrapper (mpc_wrapper .MPCWrapper ):
135+
136+ def reset (self , data , qpos , qvel , foot ):
137+ """Reset the warm start around the provided measured state."""
138+
139+ # Start from the config initial_state so any extra state entries keep
140+ # their configured default value.
141+ initial_state = (
142+ self .initial_state
143+ .at [self .qpos_slice ].set (jnp .ravel (qpos ))
144+ .at [self .qvel_slice ].set (jnp .ravel (qvel ))
145+ .at [leg_slice ].set (jnp .ravel (foot ))
146+ )
147+ return data .replace (
148+ U0 = self .initial_U0 ,
149+ X0 = jnp .tile (initial_state , (self .config .N + 1 , 1 )),
150+ V0 = self .initial_V0 ,
151+ time = jnp .asarray (0.0 , dtype = jnp .float32 ),
152+ contact_time = self .config .timer_t ,
153+ liftoff = jnp .ravel (foot [:12 ]),
154+ )
0 commit comments