Copybara import of the project:
-- 8ce7d8199ace95d0f429b0ca9169d290d167cee1 by Anas <anaselghoudane@gmail.com>: Use box midpoint to choose finite-difference direction in jacobian_fd When bounds are provided, `jacobian_fd` chooses each coordinate's finite-difference direction so the perturbation steps away from the nearer bound. It compared `x` against `0.5 * (bounds[1] - bounds[0])`, which is half the box *width*, not the box midpoint. For bounds that are not centered on the origin this selects the wrong direction, and at the lower bound the perturbation steps outside the box. Compare against the midpoint `0.5 * (bounds[0] + bounds[1])` instead. Adds a regression test checking that all residual evaluations stay within an off-center box when `x` is at the lower bound; it fails before this change and passes after. -- cfa085484c30df808362898e322de8adc6660b59 by Kevin Zakka <kevinarmandzakka@gmail.com>: Expand jacobian_fd bounds test and avoid midpoint overflow Use the distributive form `0.5*lo + 0.5*hi` instead of `0.5*(lo+hi)` to avoid overflow for extreme bound values. Expand the single-case test into a parameterized subTest covering all four boundary positions (lower/upper of positive and negative off-center boxes), and rename it to `test_jacobian_fd_respects_bounds`. COPYBARA_INTEGRATE_REVIEW=https://github.com/google-deepmind/mujoco/pull/3301 from Nas01010101:fix/minimize-fd-box-midpoint cfa085484c30df808362898e322de8adc6660b59 PiperOrigin-RevId: 933224876 Change-Id: I833cfe3324876cdeb7ca680c23d1d8a4d16adcf1
This commit is contained in:
@@ -505,7 +505,7 @@ def jacobian_fd(
|
||||
if bounds is None:
|
||||
eps_vec = eps * np.ones((n, 1))
|
||||
else:
|
||||
mid = 0.5 * (bounds[1] - bounds[0])
|
||||
mid = 0.5 * bounds[0] + 0.5 * bounds[1]
|
||||
eps_vec = np.where(x > mid, -eps, eps)
|
||||
eps_vec *= np.maximum(1.0, np.abs(x))
|
||||
eps_vec = (eps_vec + x) - x
|
||||
|
||||
@@ -156,6 +156,31 @@ class MinimizeTest(absltest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
minimize.least_squares(x0, residual, bounds=bounds, output=out)
|
||||
|
||||
def test_jacobian_fd_respects_bounds(self) -> None:
|
||||
# jacobian_fd must step inward from whichever bound x sits on, which
|
||||
# requires comparing x to the box midpoint (lo+hi)/2, not the half-width.
|
||||
eps = np.float64(np.finfo(np.float64).eps ** 0.5)
|
||||
cases = {
|
||||
'lower_positive_box': (10.0, 20.0, 10.0),
|
||||
'upper_positive_box': (10.0, 20.0, 20.0),
|
||||
'lower_negative_box': (-20.0, -10.0, -20.0),
|
||||
'upper_negative_box': (-20.0, -10.0, -10.0),
|
||||
}
|
||||
for name, (lo, hi, x0) in cases.items():
|
||||
with self.subTest(name):
|
||||
bounds = [np.array([[lo]]), np.array([[hi]])]
|
||||
x = np.array([[x0]])
|
||||
evaluated = []
|
||||
|
||||
def residual(xx, _ev=evaluated):
|
||||
_ev.append(np.asarray(xx, dtype=np.float64).copy())
|
||||
return np.atleast_2d(np.sum(xx, axis=0))
|
||||
|
||||
minimize.jacobian_fd(residual, x, residual(x), eps, 0, bounds)
|
||||
pts = np.concatenate([e.ravel() for e in evaluated])
|
||||
self.assertGreaterEqual(pts.min(), lo)
|
||||
self.assertLessEqual(pts.max(), hi)
|
||||
|
||||
def test_iter_callback(self) -> None:
|
||||
def residual(x):
|
||||
return np.stack([1 - x[0, :], 10 * (x[1, :] - x[0, :] ** 2)])
|
||||
|
||||
Reference in New Issue
Block a user