Files
Mujoco_WASM/mjx/mujoco/mjx/_src/math_test.py
T
Erik Frey 8f9c690c85 Add MJX and bump version number to 3.0.0.
Co-authored-by: Baruch Tabanpour <btaba@google.com>
PiperOrigin-RevId: 574327508
Change-Id: Ia9b62fbc929c6869dfcec87636b2e10d405a1060
2023-10-18 10:33:29 +01:00

203 lines
7.3 KiB
Python

# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for math."""
from absl.testing import absltest
from absl.testing import parameterized
import jax.numpy as jp
from mujoco.mjx._src import math
import numpy as np
def _get_rand_point(seed=None):
if seed is not None:
np.random.seed(seed)
verts = np.random.randn(1, 3)
return verts[0, :]
def _get_rand_line_segment(seed=None):
if seed is not None:
np.random.seed(seed)
verts = np.random.randn(2, 3)
return verts[0, :], verts[1, :]
def _get_rand_unit(seed: int):
np.random.seed(seed)
theta = np.random.random(1) * 2 * np.pi
a = (np.random.random(1) - 0.5) * 2.0
phi = np.arccos(a)
x = np.sin(phi) * np.cos(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(phi)
return jp.array([x, y, z]).squeeze()
class OrthoganalsTest(parameterized.TestCase):
"""Tests the orthogonals function."""
@parameterized.parameters(range(30))
def test_orthogonals(self, i):
a = _get_rand_unit(i)
b, c = math.orthogonals(a)
np.testing.assert_almost_equal(jp.linalg.norm(a), 1)
np.testing.assert_almost_equal(jp.linalg.norm(b), 1)
np.testing.assert_almost_equal(jp.linalg.norm(c), 1)
self.assertAlmostEqual(np.abs(a.dot(b)), 0, 6)
self.assertAlmostEqual(np.abs(b.dot(c)), 0, 6)
self.assertAlmostEqual(np.abs(a.dot(c)), 0, 6)
def _minimize(fn, sample_fn, lb, ub, tol, max_iter=20, seed=42):
"""Minimize a function using the cross-entropy method."""
assert lb.shape == ub.shape, "bounds need to have the same shape"
np.random.seed(seed)
i, n = 0, 1_000
mu = (ub + lb) * 0.5
sigma = (ub - lb) * 0.5
size = lb.shape[0]
val, prev_val = fn(mu), None
while prev_val is None or np.abs(val - prev_val) > tol:
params = sample_fn(mu, sigma, n, size, lb, ub)
vals = np.array([fn(p) for p in params])
if val < vals.min(): # early exit
return mu
idx = vals.argsort()
best_idx = idx[: int(n * 0.05)]
mu = params[best_idx].mean(axis=0)
sigma = params[best_idx].std(axis=0) + 1e-10
prev_val = val
val = fn(mu)
i += 1
if i == max_iter:
break
return mu
def _closest_segment_to_segment_points(a0, a1, b0, b1):
dir_a = a1 - a0
len_a = np.sqrt(dir_a.dot(dir_a))
half_len_a = len_a / 2
dir_a = dir_a / len_a
dir_b = b1 - b0
len_b = np.sqrt(dir_b.dot(dir_b))
half_len_b = len_b / 2
dir_b = dir_b / len_b
a_mid = a0 + dir_a * half_len_a
b_mid = b0 + dir_b * half_len_b
# Parametrize both line segments.
def fn(t):
best_a = a_mid + dir_a * t[0]
best_b = b_mid + dir_b * t[1]
return (best_a - best_b).dot(best_a - best_b)
def sample_fn(mu, sigma, n, size, lb, ub):
params = np.random.normal(mu, sigma, size=(n, size))
params = np.clip(params, lb, ub)
return params
lb = np.array([-half_len_a, -half_len_b])
ub = np.array([half_len_a, half_len_b])
ta, tb = _minimize(fn, sample_fn, lb, ub, tol=1e-4)
best_a = a_mid + dir_a * ta
best_b = b_mid + dir_b * tb
return best_a, best_b
class ClosestSegmentSegmentPointsTest(parameterized.TestCase):
"""Tests for closest segment-to-segment points."""
def test_closest_segments_points(self):
a0 = jp.array([0.73432405, 0.12372768, 0.20272314])
a1 = jp.array([1.10600128, 0.88555209, 0.65209485])
b0 = jp.array([0.85599262, 0.61736299, 0.9843583])
b1 = jp.array([1.84270939, 0.92891793, 1.36343326])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [1.09063, 0.85404, 0.63351], 5)
self.assertSequenceAlmostEqual(best_b, [0.99596, 0.66156, 1.03813], 5)
def test_intersecting_segments(self):
"""Tests segments that intersect."""
a0, a1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, 1.0])
b0, b1 = jp.array([-1.0, 0.0, 0.0]), jp.array([1.0, 0.0, 0.0])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.0, 0.0, 0.0], 5)
self.assertSequenceAlmostEqual(best_b, [0.0, 0.0, 0.0], 5)
def test_intersecting_lines(self):
"""Tests that intersecting lines get clipped."""
a0, a1 = jp.array([0.2, 0.2, 0.0]), jp.array([1.0, 1.0, 0.0])
b0, b1 = jp.array([0.2, 0.4, 0.0]), jp.array([1.0, 2.0, 0.0])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.3, 0.3, 0.0], 2)
self.assertSequenceAlmostEqual(best_b, [0.2, 0.4, 0.0], 2)
def test_parallel_segments(self):
"""Tests that parallel segments have closest points at the midpoint."""
a0, a1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, 1.0])
b0, b1 = jp.array([1.0, 0.0, -1.0]), jp.array([1.0, 0.0, 1.0])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.0, 0.0, 0.0], 5)
self.assertSequenceAlmostEqual(best_b, [1.0, 0.0, 0.0], 5)
def test_parallel_offset_segments(self):
"""Tests that offset parallel segments are close at segment endpoints."""
a0, a1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, 1.0])
b0, b1 = jp.array([1.0, 0.0, 1.0]), jp.array([1.0, 0.0, 3.0])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.0, 0.0, 1.0], 5)
self.assertSequenceAlmostEqual(best_b, [1.0, 0.0, 1.0], 5)
def test_zero_length_segments(self):
"""Test that zero length segments don't return NaNs."""
a0, a1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, -1.0])
b0, b1 = jp.array([1.0, 0.0, 0.1]), jp.array([1.0, 0.0, 0.1])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.0, 0.0, -1.0], 5)
self.assertSequenceAlmostEqual(best_b, [1.0, 0.0, 0.1], 5)
def test_overlapping_segments(self):
"""Tests that perfectly overlapping segments intersect at the midpoints."""
a0, a1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, 1.0])
b0, b1 = jp.array([0.0, 0.0, -1.0]), jp.array([0.0, 0.0, 1.0])
best_a, best_b = math.closest_segment_to_segment_points(a0, a1, b0, b1)
self.assertSequenceAlmostEqual(best_a, [0.0, 0.0, 0.0], 5)
self.assertSequenceAlmostEqual(best_b, [0.0, 0.0, 0.0], 5)
params = list(zip(np.repeat(np.arange(10), 10), np.tile(np.arange(10), 10)))
@parameterized.parameters(*params)
def test_closest_segment_to_segment_points(self, i, j):
a0, a1 = _get_rand_line_segment(i)
b0, b1 = _get_rand_line_segment(j)
expected = _closest_segment_to_segment_points(a0, a1, b0, b1)
ans = math.closest_segment_to_segment_points(a0, a1, b0, b1)
expected_dist = (expected[0] - expected[1]).dot(expected[0] - expected[1])
test_dist = (ans[0] - ans[1]).dot(ans[0] - ans[1])
self.assertAlmostEqual(expected_dist, test_dist, 4)
if __name__ == "__main__":
absltest.main()