Helpers for checking your own implementation against the library's.
The course pattern: write the function yourself, then compare it with the screws version
on random inputs. Nothing here does robotics; it makes the comparison short to write.
random_rotation
random_rotation(rng=None)
A random rotation matrix: the exponential of a random axis-angle with theta in [0, pi).
Source code in screws/testing.py
| def random_rotation(rng=None) -> np.ndarray:
"""A random rotation matrix: the exponential of a random axis-angle with theta in [0, pi)."""
rng = _rng(rng)
axis = rng.normal(size=3)
axis /= np.linalg.norm(axis)
return exp3(vec_to_so3(axis * rng.uniform(0.0, np.pi)))
|
random_transform(rng=None, *, scale=1.0)
A random homogeneous transformation: random rotation, position uniform in [-scale, scale]^3.
Source code in screws/testing.py
| def random_transform(rng=None, *, scale: float = 1.0) -> np.ndarray:
"""A random homogeneous transformation: random rotation, position uniform in [-scale, scale]^3."""
rng = _rng(rng)
return rp_to_transform(random_rotation(rng), rng.uniform(-scale, scale, size=3))
|
random_theta
random_theta(robot, rng=None)
A random joint vector within robot.joint_limits; [-pi, pi) for joints without a finite bound.
Source code in screws/testing.py
| def random_theta(robot, rng=None) -> np.ndarray:
"""A random joint vector within robot.joint_limits; [-pi, pi) for joints without a finite bound."""
rng = _rng(rng)
if robot.joint_limits is None:
return rng.uniform(-np.pi, np.pi, size=robot.n)
lo = np.where(np.isfinite(robot.joint_limits[:, 0]), robot.joint_limits[:, 0], -np.pi)
hi = np.where(np.isfinite(robot.joint_limits[:, 1]), robot.joint_limits[:, 1], np.pi)
return rng.uniform(lo, hi)
|
assert_close
assert_close(a, b, *, atol=1e-09, what='arrays')
Assert a and b agree to atol, or fail naming the largest discrepancy and where it is.
Source code in screws/testing.py
47
48
49
50
51
52
53
54
55
56
57
58 | def assert_close(a, b, *, atol: float = 1e-9, what: str = "arrays") -> None:
"""Assert a and b agree to atol, or fail naming the largest discrepancy and where it is."""
a, b = np.asarray(a, dtype=float), np.asarray(b, dtype=float)
if a.shape != b.shape:
raise AssertionError(f"{what} differ in shape: {a.shape} vs {b.shape}")
diff = np.abs(a - b)
if diff.size and diff.max() > atol:
idx = np.unravel_index(int(np.argmax(diff)), diff.shape)
raise AssertionError(
f"{what} differ by {diff.max():.3e} at index {tuple(int(i) for i in idx)}: "
f"{a[idx]!r} vs {b[idx]!r} (atol {atol:g})"
)
|
check
check(mine, reference, cases, *, atol=1e-09)
Run mine and reference on each case and fail on the first disagreement.
A tuple case is unpacked as positional arguments; anything else is the single argument.
Source code in screws/testing.py
| def check(mine: Callable, reference: Callable, cases: Iterable, *, atol: float = 1e-9) -> None:
"""Run mine and reference on each case and fail on the first disagreement.
A tuple case is unpacked as positional arguments; anything else is the single argument.
"""
for k, case in enumerate(cases):
args = case if isinstance(case, tuple) else (case,)
assert_close(mine(*args), reference(*args), atol=atol, what=f"case {k}: mine vs reference")
|