From cee60cb88caffcb4de6904c005ea0b265193e6a5 Mon Sep 17 00:00:00 2001 From: paleziart <paleziart@laas.fr> Date: Wed, 9 Dec 2020 09:33:21 +0100 Subject: [PATCH] Compute all jacobians then fetch them + Testing box QP --- scripts/QP_WBC.py | 231 ++++++++++++++++++++++++++++++++++++++-- scripts/solo12InvKin.py | 9 +- 2 files changed, 229 insertions(+), 11 deletions(-) diff --git a/scripts/QP_WBC.py b/scripts/QP_WBC.py index afdcc744..4b209ccf 100644 --- a/scripts/QP_WBC.py +++ b/scripts/QP_WBC.py @@ -17,6 +17,7 @@ class controller(): self.invKin = Solo12InvKin(dt) # Inverse Kinematics object self.qp_wbc = QP_WBC() # QP of the WBC + self.box_qp_wbc = BOX_QP_WBC() # QP of the WBC self.x = np.zeros(18) # solution of WBC QP self.error = False # Set to True when an error happens in the controller @@ -74,7 +75,7 @@ class controller(): ddq_cmd = np.array([self.invKin.refreshAndCompute(q.copy(), dq.copy(), x_cmd, contacts, planner)]).T # Log position, velocity and acceleration references for the feet - indexes = [10, 18, 26, 34] + """indexes = [10, 18, 26, 34] for i in range(4): self.log_feet_pos[:, i, self.k_log] = self.invKin.rdata.oMf[indexes[i]].translation self.log_feet_err[:, i, self.k_log] = self.invKin.pfeet_err[i] @@ -83,26 +84,38 @@ class controller(): # + np.array([[0.0, 0.0, q[2, 0] - planner.h_ref]]).T self.log_feet_pos_target[:, :, self.k_log] = planner.goals[:, :] self.log_feet_vel_target[:, :, self.k_log] = planner.vgoals[:, :] - self.log_feet_acc_target[:, :, self.k_log] = planner.agoals[:, :] + self.log_feet_acc_target[:, :, self.k_log] = planner.agoals[:, :]""" self.tac = time() + + """self.box_qp_wbc.compute_matrices(self.invKin.rmodel, self.invKin.rdata, q, dq, ddq_cmd, np.array([f_cmd]).T, contacts) + self.box_qp_wbc.create_weight_matrices()""" + # Solve QP problem + self.box_qp_wbc.compute(self.invKin.rmodel, self.invKin.rdata, + q.copy(), dq.copy(), ddq_cmd, np.array([f_cmd]).T, contacts) + # Solve QP problem - self.qp_wbc.compute(self.invKin.robot.model, self.invKin.robot.data, + self.qp_wbc.compute(self.invKin.rmodel, self.invKin.rdata, q.copy(), dq.copy(), ddq_cmd, np.array([f_cmd]).T, contacts) """if dq[0, 0] > 0.4: from IPython import embed embed()""" + # Retrieve joint torques + tmp_res = self.box_qp_wbc.get_joint_torques(self.invKin.rmodel, self.invKin.rdata, q, dq, ddq_cmd) self.tau_ff[:] = self.qp_wbc.get_joint_torques().ravel() self.toc = time() + """from IPython import embed + embed()""" + # Retrieve desired positions and velocities self.vdes[:, 0] = self.invKin.dq_cmd # (dq + ddq_cmd * self.dt).ravel() # v des in world frame self.qdes[:] = self.invKin.q_cmd # pin.integrate(self.invKin.robot.model, q, self.vdes * self.dt) - # Double integration of ddq_cmd + delta_ddq + """# Double integration of ddq_cmd + delta_ddq # dq[2, 0] = 0.0 self.qdes[2] self.vint[:, 0] = (dq + ddq_cmd * self.dt).ravel() # in world frame # self.vint[0:3, 0:1] = self.invKin.rot.transpose() @ self.vint[0:3, 0:1] # velocity needs to be in base frame for pin.integrate @@ -134,7 +147,7 @@ class controller(): self.log_vdes[:, self.k_log] = self.vdes[6:, 0] self.log_contacts[:, self.k_log] = contacts - self.log_tstamps[self.k_log] = clock() + self.log_tstamps[self.k_log] = clock()""" """if dq[0, 0] > 0.02: from IPython import embed @@ -144,6 +157,11 @@ class controller(): self.tuc = time() + """self.tic = 0.0 + self.tac = 0.0 + self.toc = 0.0 + self.tuc = 0.0""" + return 0 def show_logs(self): @@ -404,9 +422,8 @@ class QP_WBC(): self.JcT = np.zeros((18, 12)) for i in range(4): if contacts[i]: - self.JcT[:, (3*i):(3*(i+1))] = pin.computeFrameJacobian(model, data, q, indexes[i], - pin.LOCAL_WORLD_ALIGNED)[:3, :].transpose() - + self.JcT[:, (3*i):(3*(i+1))] = pin.getFrameJacobian(model, data, indexes[i], + pin.LOCAL_WORLD_ALIGNED)[:3, :].transpose() self.ML_full[:6, :6] = - self.A[:6, :6] self.ML_full[:6, 6:] = self.JcT[:6, :] @@ -497,6 +514,204 @@ class QP_WBC(): - self.JcT @ (self.f_cmd + np.array([self.x[6:]]).transpose()))[6:, ] +class BOX_QP_WBC(): + + def __init__(self): + + # Set to True after the creation of the QP problem during the first call of the solver + self.initialized = False + + # Weight matrices + self.Q1 = 0.1 * np.eye(6) + self.Q2 = 1.0 * np.eye(12) + + # Friction coefficient + self.mu = 0.9 + + # Generatrix matrix + self.Gk = np.array([[self.mu, self.mu, -self.mu, -self.mu], + [self.mu, -self.mu, self.mu, -self.mu], + [1.0, 1.0, 1.0, 1.0]]) + self.G = np.zeros((12, 16)) + for k in range(4): + self.G[(3*k):(3*(k+1)), (4*k):(4*(k+1))] = self.Gk + + # QP OSQP object + self.prob = osqp.OSQP() + + # ML matrix + self.ML = scipy.sparse.csc.csc_matrix( + np.ones((6 + 20, 18)), shape=(6 + 20, 18)) + self.ML_full = np.zeros((6 + 20, 18)) + + self.C = np.zeros((5, 3)) # Force constraints + self.C[[0, 1, 2, 3] * 2 + [4], [0, 0, 1, 1, 2, 2, 2, 2, 2] + ] = np.array([1, -1, 1, -1, -self.mu, -self.mu, -self.mu, -self.mu, -1]) + for i in range(4): + self.ML_full[(6+5*i): (6+5*(i+1)), (6+3*i): (6+3*(i+1))] = self.C + + # Relaxation of acceleration + self.delta_ddq = np.zeros((18, 1)) + + # NK matrix + self.NK = np.zeros((6 + 20, 1)) + + # NK_inf is the lower bound + self.NK_inf = np.zeros((6 + 20, )) + self.inf_lower_bound = -np.inf * np.ones((20,)) + self.inf_lower_bound[4:: 5] = - 25.0 # - maximum normal force + self.NK_inf[: 6] = self.NK[: 6, 0] + self.NK_inf[6:] = self.inf_lower_bound + + # Mass matrix + self.M = np.zeros((18, 18)) + self.RNEA = np.zeros((6, 1)) + + # Indexes of feet frames in this order: [FL, FR, HL, HR] + self.indexes = [10, 18, 26, 34] + + # Create weight matrices + self.create_weight_matrices() + + def compute_matrices(self, model, data, q, v, ddq_cmd, f_cmd, contacts): + """TODO + """ + + # Compute the joint space inertia matrix M by using the Composite Rigid Body Algorithm + self.M = pin.crba(model, data, q) + + # Contact Jacobian + self.Jc = np.zeros((12, 18)) + for i in range(4): + if contacts[i]: + self.Jc[(3*i):(3*(i+1)), :] = pin.getFrameJacobian(model, data, self.indexes[i], + pin.LOCAL_WORLD_ALIGNED)[:3, :] + + # Compute all matrices of the Box QP problem + self.f_cmd = f_cmd + self.Y = self.M[:6, :6] + self.X = self.Jc[:, :6].transpose() + self.Yinv = np.linalg.pinv(self.Y) + self.A = self.Yinv @ self.X + self.RNEA[:, 0] = pin.rnea(model, data, q, v, ddq_cmd)[:6] + self.gamma = self.Yinv @ ((self.X @ f_cmd) - self.RNEA) + self.H = (self.A.transpose() @ self.Q1) @ self.A + self.Q2 + self.g = (self.A.transpose() @ self.Q1) @ self.gamma + self.Pw = (self.G.transpose() @ self.H) @ self.G + self.Qw = (self.G.transpose() @ self.g) - ((self.G.transpose() @ self.H) @ f_cmd) + + """from IPython import embed + embed()""" + + return 0 + + def create_weight_matrices(self): + """Create the weight matrices P and q in the cost function 1/2 x^T.P.x + x^T.q of the QP problem + """ + + # Number of states + n_x = 16 + + # Declaration of the P matrix in "x^T.P.x + x^T.q" + # P_row, _col and _data satisfy the relationship P[P_row[k], P_col[k]] = P_data[k] + P_row = np.array([], dtype=np.int64) + P_col = np.array([], dtype=np.int64) + P_data = np.array([], dtype=np.float64) + + # Define weights for the x-x_ref components of the optimization vector + P_row = np.tile(np.arange(0, n_x, 1), n_x) + P_col = np.repeat(np.arange(0, n_x, 1), n_x) + P_data = np.ones((n_x*n_x,)) + + # Convert P into a csc matrix for the solver + self.P = scipy.sparse.csc.csc_matrix( + (P_data, (P_row, P_col)), shape=(n_x, n_x)) + + # Declaration of the Q matrix in "x^T.P.x + x^T.Q" + self.Q = np.zeros(n_x,) + + self.box_sup = 25.0 * np.ones((16, )) + self.box_low = np.zeros((16, )) + + return 0 + + def update_matrices(self): + """Update the weight matrices P and q in the cost function 1/2 x^T.P.x + x^T.q of the QP problem + """ + + #if self.initialized: + self.Pw[np.abs(self.Pw) < 1e-6] = 0.0 + self.Qw[np.abs(self.Qw) < 1e-6] = 0.0 + + self.P.data[:] = self.Pw.ravel(order="F") + self.Q[:] = self.Qw.ravel() + + def call_solver(self): + """Create an initial guess and call the solver to solve the QP problem + + Args: + k (int): number of MPC iterations since the start of the simulation + """ + + """from IPython import embed + embed()""" + # Setup the solver (first iteration) then just update it + if not self.initialized: # Setup the solver with the matrices + self.prob.setup(P=self.P, q=self.Q, A=scipy.sparse.csc.csc_matrix(np.eye(16), shape=(16, 16)), + l=self.box_low, u=self.box_sup, verbose=False) + #self.prob.update_settings(eps_abs=1e-5) + #self.prob.update_settings(eps_rel=1e-5) + # self.prob.update_settings(time_limit=5e-4) + self.initialized = True + """from IPython import embed + embed() + self.update_matrices() + self.prob.update(Px=self.P.data, Px_idx=np.arange(0, len(self.P.data), 1), q=self.Q[:])""" + else: # Code to update the QP problem without creating it again + try: + #self.prob.update(Px=self.P.data, Px_idx=np.arange(0, len(self.P.data), 1), q=self.Q[:]) + self.prob.setup(P=self.P, q=self.Q, A=scipy.sparse.csc.csc_matrix(np.eye(16), shape=(16, 16)), + l=self.box_low, u=self.box_sup, verbose=False) + a = 1 + except ValueError: + print("Bound Problem") + # self.prob.warm_start(x=self.x) + + # Run the solver to solve the QP problem + self.sol = self.prob.solve() + self.x = self.sol.x + + self.f_res = self.G @ self.x.reshape((16, 1)) + self.ddq_res = self.A @ (self.f_res - self.f_cmd) + self.gamma + """from IPython import embed + embed()""" + print(self.f_res.ravel()) + print(self.x.ravel()) + from IPython import embed + embed() + + return 0 + + def compute(self, model, data, q, dq, ddq_cmd, f_cmd, contacts): + + self.compute_matrices(model, data, q, dq, ddq_cmd, f_cmd, contacts) + self.update_matrices() + self.call_solver() + + return 0 + + def get_joint_torques(self, model, data, q, v, ddq_cmd): + + self.delta_ddq[:6, 0:1] = self.ddq_res + + return (pin.rnea(model, data, q, v, ddq_cmd+self.delta_ddq) + (self.Jc.transpose() @ self.f_res).ravel())[6:, ] + + """self.delta_ddq[:6, 0] = self.x[:6] + + return (self.A @ (self.ddq_cmd + self.delta_ddq) + np.array([self.NLE]).transpose() + - self.JcT @ (self.f_cmd + np.array([self.x[6:]]).transpose()))[6:, ]""" + + if __name__ == "__main__": # Load the URDF model diff --git a/scripts/solo12InvKin.py b/scripts/solo12InvKin.py index 4a36fecb..ef7398da 100644 --- a/scripts/solo12InvKin.py +++ b/scripts/solo12InvKin.py @@ -105,6 +105,7 @@ class Solo12InvKin: afeet = [] self.pfeet_err = [] vfeet_ref = [] + pin.computeJointJacobians(self.rmodel, self.rdata, q) pin.forwardKinematics(self.rmodel, self.rdata, q, dq, np.zeros(self.rmodel.nv)) pin.updateFramePlacements(self.rmodel, self.rdata) @@ -116,7 +117,9 @@ class Solo12InvKin: vref = self.feet_velocity_ref[i_ee] aref = self.feet_acceleration_ref[i_ee] - J1 = pin.computeFrameJacobian(self.robot.model, self.robot.data, q, idx, pin.LOCAL_WORLD_ALIGNED)[:3] + J1 = pin.getFrameJacobian(self.robot.model, self.robot.data, idx, pin.LOCAL_WORLD_ALIGNED)[:3] + # J1 = pin.computeFrameJacobian(self.robot.model, self.robot.data, q, idx, pin.LOCAL_WORLD_ALIGNED)[:3] + # print(np.array_equal(J1, J1b)) e1 = ref-pos acc1 = -self.Kp_flyingfeet*(pos-ref) - self.Kd_flyingfeet*(nu.linear-vref) + aref if self.flag_in_contact[i_ee]: @@ -137,7 +140,7 @@ class Solo12InvKin: pos = self.rdata.oMf[idx].translation nu = pin.getFrameVelocity(self.rmodel, self.rdata, idx, pin.LOCAL_WORLD_ALIGNED) ref = self.base_position_ref - Jbasis = pin.computeFrameJacobian(self.robot.model, self.robot.data, q, idx, pin.LOCAL_WORLD_ALIGNED)[:3] + Jbasis = pin.getFrameJacobian(self.robot.model, self.robot.data, idx, pin.LOCAL_WORLD_ALIGNED)[:3] e_basispos = ref - pos accbasis = -self.Kp_base_position*(pos-ref) - self.Kd_base_position*(nu.linear-self.base_linearvelocity_ref) drift = np.zeros(3) @@ -157,7 +160,7 @@ class Solo12InvKin: rot = self.rdata.oMf[idx].rotation nu = pin.getFrameVelocity(self.rmodel, self.rdata, idx, pin.LOCAL_WORLD_ALIGNED) rotref = self.base_orientation_ref - Jwbasis = pin.computeFrameJacobian(self.robot.model, self.robot.data, q, idx, pin.LOCAL_WORLD_ALIGNED)[3:] + Jwbasis = pin.getFrameJacobian(self.robot.model, self.robot.data, idx, pin.LOCAL_WORLD_ALIGNED)[3:] e_basisrot = -rotref @ pin.log3(rotref.T@rot) accwbasis = -self.Kp_base_orientation * \ rotref @ pin.log3(rotref.T@rot) - self.Kd_base_orientation*(nu.angular - self.base_angularvelocity_ref) -- GitLab