Skip to content

Commit c2bed0b

Browse files
authored
Merge pull request #110 from kevinzakka/add-more-tests
Add more tests to configuration limit.
2 parents 47088ab + f45e59e commit c2bed0b

File tree

5 files changed

+55
-0
lines changed

5 files changed

+55
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## Unreleased
6+
7+
### Added
8+
9+
* Add more tests to `test_configuration_limit.py` and `test_velocity_limit.py`.
10+
511
## [0.0.13] - 2025-09-12
612

713
### Bugfix

mink/lie/se3.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ def __eq__(self, other: object) -> bool:
3838
return NotImplemented
3939
return np.array_equal(self.wxyz_xyz, other.wxyz_xyz)
4040

41+
def __hash__(self) -> int:
42+
return hash(self.wxyz_xyz.tobytes())
43+
4144
def copy(self) -> SE3:
4245
return SE3(wxyz_xyz=np.array(self.wxyz_xyz))
4346

mink/lie/so3.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ def __eq__(self, other: object) -> bool:
4949
return NotImplemented
5050
return np.array_equal(self.wxyz, other.wxyz)
5151

52+
def __hash__(self) -> int:
53+
return hash(self.wxyz.tobytes())
54+
5255
def parameters(self) -> np.ndarray:
5356
return self.wxyz
5457

tests/test_configuration_limit.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import numpy as np
55
from absl.testing import absltest
66
from robot_descriptions.loaders.mujoco import load_robot_description
7+
from scipy.optimize import linprog
78

89
from mink import Configuration
910
from mink.exceptions import LimitDefinitionError
@@ -155,6 +156,40 @@ def test_configuration_limit_repulsion(self, tol=1e-10):
155156
self.assertLess(np.max(h), slack_vel * dt + tol)
156157
self.assertGreater(np.min(h), -slack_vel * dt - tol)
157158

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+
158193

159194
if __name__ == "__main__":
160195
absltest.main()

tests/test_velocity_limit.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ def test_that_freejoint_raises_error(self):
140140
expected_error_message = "Free joint floating is not supported"
141141
self.assertEqual(str(cm.exception), expected_error_message)
142142

143+
def test_velocity_limit_scales_with_dt(self):
144+
"""RHS scales linearly with dt (sanity check for Δq = v*dt)."""
145+
vlim = VelocityLimit(self.model, self.velocities)
146+
G1, h1 = vlim.compute_qp_inequalities(self.configuration, dt=0.1)
147+
G2, h2 = vlim.compute_qp_inequalities(self.configuration, dt=0.2)
148+
assert np.allclose(G1, G2)
149+
assert np.allclose(h2, 2.0 * h1)
150+
143151

144152
if __name__ == "__main__":
145153
absltest.main()

0 commit comments

Comments
 (0)