static-contact-dynamics.py
Go to the documentation of this file.
1 from pathlib import Path
2 
3 import numpy as np
4 import pinocchio as pin
5 
6 np.set_printoptions(linewidth=np.inf)
7 
8 # ----- PROBLEM STATEMENT ------
9 #
10 # We want to find the contact forces and torques required to stand still at a
11 # configuration 'q0'.
12 # We assume 3D contacts at each of the feet
13 #
14 # The dynamic equation would look like:
15 #
16 # M*q_ddot + g(q) + C(q, q_dot) = tau + J^T*lambda --> (for the static case) --> g(q) =
17 # tau + Jc^T*lambda (1).
18 
19 # ----- SOLVING STRATEGY ------
20 
21 # Split the equation between the base link (_bl) joint and the rest of the joints (_j).
22 # That is,
23 #
24 # | g_bl | | 0 | | Jc__feet_bl.T | | l1 |
25 # | g_j | = | tau | + | Jc__feet_j.T | * | l2 | (2)
26 # | l3 |
27 # | l4 |
28 
29 # First, find the contact forces l1, l2, l3, l4 (these are 3 dimensional) by solving for
30 # the first 6 rows of (2).
31 # That is,
32 #
33 # g_bl = Jc__feet_bl.T * | l1 |
34 # | l2 | (3)
35 # | l3 |
36 # | l4 |
37 #
38 # Thus we find the contact froces by computing the jacobian pseudoinverse,
39 #
40 # | l1 | = pinv(Jc__feet_bl.T) * g_bl (4)
41 # | l2 |
42 # | l3 |
43 # | l4 |
44 #
45 # Now, we can find the necessary torques using the bottom rows in (2). That is,
46 #
47 # | l1 |
48 # tau = g_j - Jc__feet_j.T * | l2 | (5)
49 # | l3 |
50 # | l4 |
51 
52 # ----- SOLUTION ------
53 
54 # 0. DATA
55 pinocchio_model_dir = Path(__file__).parent.parent / "models"
56 
57 model_path = pinocchio_model_dir / "example-robot-data/robots"
58 mesh_dir = pinocchio_model_dir
59 urdf_filename = "solo12.urdf"
60 urdf_model_path = model_path / "solo_description/robots" / urdf_filename
61 
62 model, collision_model, visual_model = pin.buildModelsFromUrdf(
63  urdf_model_path, mesh_dir, pin.JointModelFreeFlyer()
64 )
65 data = model.createData()
66 
67 q0 = np.array(
68  [
69  0.0,
70  0.0,
71  0.235,
72  0.0,
73  0.0,
74  0.0,
75  1.0,
76  0.0,
77  0.8,
78  -1.6,
79  0.0,
80  -0.8,
81  1.6,
82  0.0,
83  0.8,
84  -1.6,
85  0.0,
86  -0.8,
87  1.6,
88  ]
89 )
90 v0 = np.zeros(model.nv)
91 a0 = np.zeros(model.nv)
92 
93 # 1. GRAVITY TERM
94 
95 # We compute the gravity terms by using the ID at desired configuration q0, with
96 # velocity and acceleration being 0. I.e., ID with a = v = 0.
97 g_grav = pin.rnea(model, data, q0, v0, a0)
98 
99 g_bl = g_grav[:6]
100 g_j = g_grav[6:]
101 
102 # 2. FIND CONTACTS
103 
104 # First, we set the frame for our contacts. We assume the contacts are placed at the
105 # following 4 frames and they are 3D.
106 feet_names = ["FL_FOOT", "FR_FOOT", "HL_FOOT", "HR_FOOT"]
107 feet_ids = [model.getFrameId(n) for n in feet_names]
108 bl_id = model.getFrameId("base_link")
109 ncontact = len(feet_names)
110 
111 # Now, we need to find the contact Jacobians appearing in (1).
112 # These are the Jacobians that relate the joint velocity to the velocity of each feet
113 Js__feet_q = [
114  np.copy(pin.computeFrameJacobian(model, data, q0, id, pin.LOCAL)) for id in feet_ids
115 ]
116 
117 Js__feet_bl = [np.copy(J[:3, :6]) for J in Js__feet_q]
118 
119 # Notice that we can write the equation above as an horizontal stack of Jacobians
120 # transposed and vertical stack of contact forces.
121 Jc__feet_bl_T = np.zeros([6, 3 * ncontact])
122 Jc__feet_bl_T[:, :] = np.vstack(Js__feet_bl).T
123 
124 # Now I only need to do the pinv to compute the contact forces
125 
126 ls = np.linalg.pinv(Jc__feet_bl_T) @ g_bl # This is (3)
127 
128 # Contact forces at local coordinates (at each foot coordinate)
129 ls__f = np.split(ls, ncontact)
130 
131 pin.framesForwardKinematics(model, data, q0)
132 
133 # Contact forces at base link frame
134 ls__bl = []
135 for l__f, foot_id in zip(ls__f, feet_ids):
136  l_sp__f = pin.Force(l__f, np.zeros(3))
137  l_sp__bl = data.oMf[bl_id].actInv(data.oMf[foot_id].act(l_sp__f))
138  ls__bl.append(np.copy(l_sp__bl.vector))
139 
140 print("\n--- CONTACT FORCES ---")
141 for l__f, foot_id, name in zip(ls__bl, feet_ids, feet_names):
142  print(f"Contact force at foot {name} expressed at the BL is: {l__f}")
143 
144 # Notice that if we add all the contact forces are equal to the g_grav
145 print(
146  "Error between contact forces and gravity at base link: "
147  f"{np.linalg.norm(g_bl - sum(ls__bl))}"
148 )
149 
150 # 3. FIND TAU
151 # Find Jc__feet_j
152 Js_feet_j = [np.copy(J[:3, 6:]) for J in Js__feet_q]
153 
154 Jc__feet_j_T = np.zeros([12, 3 * ncontact])
155 Jc__feet_j_T[:, :] = np.vstack(Js_feet_j).T
156 
157 # Apply (5)
158 tau = g_j - Jc__feet_j_T @ ls
159 
160 # 4. CROSS CHECKS
161 
162 # INVERSE DYNAMICS
163 # We can compare this torques with the ones one would obtain when computing the ID
164 # considering the external forces in ls.
165 pin.framesForwardKinematics(model, data, q0)
166 
167 joint_names = ["FL_KFE", "FR_KFE", "HL_KFE", "HR_KFE"]
168 joint_ids = [model.getJointId(n) for n in joint_names]
169 
170 fs_ext = [pin.Force(np.zeros(6)) for _ in range(len(model.joints))]
171 for idx, joint in enumerate(model.joints):
172  if joint.id in joint_ids:
173  fext__bl = pin.Force(ls__bl[joint_ids.index(joint.id)])
174  fs_ext[idx] = data.oMi[joint.id].actInv(data.oMf[bl_id].act(fext__bl))
175 
176 tau_rnea = pin.rnea(model, data, q0, v0, a0, fs_ext)
177 
178 print("\n--- ID: JOINT TORQUES ---")
179 print(f"Tau from RNEA: {tau_rnea}")
180 print(f"Tau computed manually: {np.append(np.zeros(6), tau)}")
181 print(f"Tau error: {np.linalg.norm(np.append(np.zeros(6), tau) - tau_rnea)}")
182 
183 # FORWARD DYNAMICS
184 # We can also check the results using FD. FD with the tau we got, q0 and v0, should give
185 # 0 acceleration and the contact forces.
186 Js_feet3d_q = [np.copy(J[:3, :]) for J in Js__feet_q]
187 acc = pin.forwardDynamics(
188  model,
189  data,
190  q0,
191  v0,
192  np.append(np.zeros(6), tau),
193  np.vstack(Js_feet3d_q),
194  np.zeros(12),
195 )
196 
197 print("\n--- FD: ACC. & CONTACT FORCES ---")
198 print(f"Norm of the FD acceleration: {np.linalg.norm(acc)}")
199 print(f"Contact forces manually: {ls}")
200 print(f"Contact forces FD: {data.lambda_c}")
201 print(f"Contact forces error: {np.linalg.norm(data.lambda_c - ls)}")
pinocchio::motionSet::act
static void act(const Eigen::MatrixBase< Mat > &iV, const ForceDense< ForceDerived > &f, Eigen::MatrixBase< MatRet > const &jF)
Action of a motion set on a force object. The input motion set is represented by a 6xN matrix whose e...


pinocchio
Author(s):
autogenerated on Fri Nov 1 2024 02:41:48