Skip to content

Commit 0c712e5

Browse files
committed
fixed inverse dynamcs formulation for go2
1 parent 6d4a7e0 commit 0c712e5

2 files changed

Lines changed: 162 additions & 105 deletions

File tree

mpx/config/config_go2_inv.py

Lines changed: 143 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from mujoco.mjx._src.dataclasses import PyTreeNode
1010

1111
from mpx.jax_ocp_solvers.jax_ocp_solvers import optimizers
12+
import mpx.utils.mpc_utils as mpc_utils
1213

1314

1415
dir_path = os.path.dirname(os.path.realpath(__file__))
@@ -21,7 +22,8 @@
2122
N = 12
2223
mpc_frequency = 50.0
2324
solver_mode = "equality"
24-
equality_num_alpha = 5
25+
equality_num_alpha = 10
26+
regularization = jnp.array(1e-6)
2527

2628
timer_t = jnp.array([0.5, 0.0, 0.0, 0.5])
2729
duty_factor = jnp.array([0.65])
@@ -30,6 +32,7 @@
3032
initial_height = 0.1
3133
robot_height = 0.27
3234
use_terrain_estimation = False
35+
clearance_speed = 0.2
3336

3437
p0 = jnp.array([0.0, 0.0, robot_height])
3538
quat0 = jnp.array([1.0, 0.0, 0.0, 0.0])
@@ -65,16 +68,28 @@
6568
tau_slice = slice(nv, nv + n_joints)
6669
grf_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]))
6972
Qrot = 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
7174
Qdp = jnp.diag(jnp.array([1.0, 1.0, 1.0])) * 5.0e3
7275
Qomega = 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))
7478
Qacc = 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

7994
initial_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+
123145
def 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

146170
def 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-
234215
class 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

398428
def 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

Comments
 (0)