|
4 | 4 | import numpy as np |
5 | 5 | from absl.testing import absltest |
6 | 6 | from robot_descriptions.loaders.mujoco import load_robot_description |
| 7 | +from scipy.optimize import linprog |
7 | 8 |
|
8 | 9 | from mink import Configuration |
9 | 10 | from mink.exceptions import LimitDefinitionError |
@@ -155,6 +156,40 @@ def test_configuration_limit_repulsion(self, tol=1e-10): |
155 | 156 | self.assertLess(np.max(h), slack_vel * dt + tol) |
156 | 157 | self.assertGreater(np.min(h), -slack_vel * dt - tol) |
157 | 158 |
|
| 159 | + def test_configuration_limit_dt_invariance(self): |
| 160 | + """Inequalities are invariant to dt.""" |
| 161 | + limit = ConfigurationLimit(self.model, gain=0.95) |
| 162 | + G1, h1 = limit.compute_qp_inequalities(self.configuration, dt=1e-3) |
| 163 | + G2, h2 = limit.compute_qp_inequalities(self.configuration, dt=0.2) |
| 164 | + self.assertTrue(np.allclose(G1, G2)) |
| 165 | + self.assertTrue(np.allclose(h1, h2)) |
| 166 | + |
| 167 | + def test_feasible_step_respects_position_bounds(self, tol=1e-9): |
| 168 | + """A strictly feasible Δq (for GΔq ≤ h) keeps q_next inside [lower, upper].""" |
| 169 | + limit = ConfigurationLimit(self.model, gain=1.0) |
| 170 | + |
| 171 | + # dt is irrelevant for configuration limits; pass anything. |
| 172 | + G, h = limit.compute_qp_inequalities(self.configuration, dt=0.1) |
| 173 | + |
| 174 | + # We use linprog to construct a strictly feasible delta_q for the inequality |
| 175 | + # set `G Δq ≤ h - eps``. Shrinking the RHS by eps guarantees strict slack if a |
| 176 | + # solution exists. linprog with a zero cost is a convenient feasibility solver. |
| 177 | + eps = 1e-3 |
| 178 | + c = np.zeros(self.configuration.nv) |
| 179 | + res = linprog(c, A_ub=G, b_ub=h - eps, bounds=(None, None), method="highs") |
| 180 | + assert res.success, "Could not find a strictly feasible Δq" |
| 181 | + delta_q = res.x |
| 182 | + |
| 183 | + # Integrate one second with `v = delta_q` (so Δq = v * 1). |
| 184 | + q_next = self.configuration.integrate(delta_q, dt=1.0) |
| 185 | + |
| 186 | + # Check the next configuration is inside the true bounds for limited joints. |
| 187 | + lower = limit.lower[limit.indices] |
| 188 | + upper = limit.upper[limit.indices] |
| 189 | + qn = q_next[limit.indices] |
| 190 | + assert np.all(qn <= upper + tol) |
| 191 | + assert np.all(qn >= lower - tol) |
| 192 | + |
158 | 193 |
|
159 | 194 | if __name__ == "__main__": |
160 | 195 | absltest.main() |
0 commit comments