From 385d754fcd312c11a8308be7353e7b969328495b Mon Sep 17 00:00:00 2001 From: timmens Date: Fri, 30 Oct 2026 14:43:57 +0100 Subject: [PATCH] Preserve restrictive infinite bounds during normalization --- src/optimagic/parameters/bounds.py | 8 +++--- tests/optimagic/parameters/test_bounds.py | 34 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/optimagic/parameters/bounds.py b/src/optimagic/parameters/bounds.py index 8e26022db..a4075cb75 100644 --- a/src/optimagic/parameters/bounds.py +++ b/src/optimagic/parameters/bounds.py @@ -156,9 +156,9 @@ def get_internal_bounds( msg = "Invalid bounds. Some lower bounds are larger than upper bounds." raise InvalidBoundsError(msg) - if np.isinf(lower_flat).all(): + if np.isneginf(lower_flat).all(): lower_flat = None - if np.isinf(upper_flat).all(): + if np.isposinf(upper_flat).all(): upper_flat = None return lower_flat, upper_flat @@ -245,14 +245,14 @@ def _get_fast_path_bounds( lower_bounds = None else: lower_bounds = bounds.lower.astype(float) - if np.isinf(lower_bounds).all(): + if np.isneginf(lower_bounds).all(): lower_bounds = None if bounds.upper is None: upper_bounds = None else: upper_bounds = bounds.upper.astype(float) - if np.isinf(upper_bounds).all(): + if np.isposinf(upper_bounds).all(): upper_bounds = None if ( diff --git a/tests/optimagic/parameters/test_bounds.py b/tests/optimagic/parameters/test_bounds.py index a6c3f487a..ac71de549 100644 --- a/tests/optimagic/parameters/test_bounds.py +++ b/tests/optimagic/parameters/test_bounds.py @@ -138,6 +138,40 @@ def test_get_bounds_numpy_error(array_params): ) +@pytest.mark.parametrize("container", [np.array, list]) +@pytest.mark.parametrize( + "lower, upper, expected_lower, expected_upper", + [ + ([-np.inf, -np.inf], [np.inf, np.inf], None, None), + ([np.inf, np.inf], None, [np.inf, np.inf], None), + (None, [-np.inf, -np.inf], None, [-np.inf, -np.inf]), + ([-np.inf, np.inf], None, [-np.inf, np.inf], None), + (None, [-np.inf, np.inf], None, [-np.inf, np.inf]), + ], +) +def test_get_bounds_infinite_sides( + container, lower, upper, expected_lower, expected_upper +): + bounds = Bounds( + lower=None if lower is None else container(lower), + upper=None if upper is None else container(upper), + ) + + got_lower, got_upper = get_internal_bounds(np.ones(2), bounds=bounds) + + assert_array_equal(got_lower, expected_lower) + assert_array_equal(got_upper, expected_upper) + + +@pytest.mark.parametrize("container", [np.array, list]) +@pytest.mark.parametrize("lower, upper", [(np.inf, 5), (0, -np.inf), (np.inf, -np.inf)]) +def test_get_bounds_infinite_sides_invalid(container, lower, upper): + bounds = Bounds(lower=container([lower, lower]), upper=container([upper, upper])) + + with pytest.raises(InvalidBoundsError): + get_internal_bounds(np.ones(2), bounds=bounds) + + def test_get_fast_path_bounds_both_none(): got_lower, got_upper = _get_fast_path_bounds(Bounds(lower=None, upper=None)) assert got_lower is None