Skip to content

Commit 013db85

Browse files
committed
Propagate None bounds through get_internal_bounds
1 parent 67bc435 commit 013db85

3 files changed

Lines changed: 59 additions & 14 deletions

File tree

src/optimagic/optimizers/fides.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ def fides_internal(
183183

184184
hessian_instance = _create_hessian_updater_from_user_input(hessian_update_strategy)
185185

186-
lower_bounds = np.full_like(x, -np.inf) if lower_bounds is None else lower_bounds
187-
upper_bounds = np.full_like(x, np.inf) if upper_bounds is None else upper_bounds
186+
lower_bounds = np.full(len(x), -np.inf) if lower_bounds is None else lower_bounds
187+
upper_bounds = np.full(len(x), np.inf) if upper_bounds is None else upper_bounds
188188

189189
opt = Optimizer(
190190
fun=fun_and_jac,

src/optimagic/parameters/bounds.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ def pre_process_bounds(
6060

6161

6262
def _process_bounds_sequence(bounds: Sequence[tuple[float, float]]) -> Bounds:
63-
lower = np.full(len(bounds), -np.inf)
64-
upper = np.full(len(bounds), np.inf)
63+
lower = _fast_full_array(len(bounds), value=-np.inf)
64+
upper = _fast_full_array(len(bounds), value=np.inf)
6565

6666
for i, (lb, ub) in enumerate(bounds):
6767
if lb is not None:
@@ -76,7 +76,8 @@ def get_internal_bounds(
7676
bounds: Bounds | None = None,
7777
registry: PyTreeRegistry | None = None,
7878
add_soft_bounds: bool = False,
79-
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
79+
propagate_none: bool = False,
80+
) -> tuple[NDArray[np.float64] | None, NDArray[np.float64] | None]:
8081
"""Create consolidated and flattened bounds for params.
8182
8283
If params is a DataFrame with value column, the user provided bounds are
@@ -95,6 +96,9 @@ def get_internal_bounds(
9596
add_soft_bounds: If True, the element-wise maximum (minimum) of the lower and
9697
soft_lower (upper and soft_upper) bounds are taken. If False, the lower
9798
(upper) bounds are returned.
99+
propagate_none: If True, None values in bounds are propagated to the output.
100+
If False, None values are replaced with -np.inf for the lower bound and
101+
np.inf for the upper bound.
98102
99103
Returns:
100104
Consolidated and flattened lower_bounds.
@@ -112,6 +116,7 @@ def get_internal_bounds(
112116
return _get_fast_path_bounds(
113117
params=params,
114118
bounds=bounds,
119+
propagate_none=propagate_none,
115120
)
116121

117122
registry = get_registry(extended=True) if registry is None else registry
@@ -213,7 +218,7 @@ def _is_fast_path(params: PyTree, bounds: Bounds, add_soft_bounds: bool) -> bool
213218
if not _is_1d_array(params):
214219
out = False
215220

216-
for bound in bounds.lower, bounds.upper:
221+
for bound in (bounds.lower, bounds.upper):
217222
if not (_is_1d_array(bound) or bound is None):
218223
out = False
219224
return out
@@ -224,22 +229,37 @@ def _is_1d_array(candidate: Any) -> bool:
224229

225230

226231
def _get_fast_path_bounds(
227-
params: PyTree, bounds: Bounds
228-
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
232+
params: NDArray[np.float64], bounds: Bounds, propagate_none: bool = False
233+
) -> tuple[NDArray[np.float64] | None, NDArray[np.float64] | None]:
229234
if bounds.lower is None:
230-
# faster than np.full
231-
lower_bounds = np.array([-np.inf] * len(params))
235+
if propagate_none:
236+
lower_bounds = None
237+
else:
238+
lower_bounds = _fast_full_array(len(params), value=-np.inf)
232239
else:
233240
lower_bounds = bounds.lower.astype(float)
234241

235242
if bounds.upper is None:
236-
# faster than np.full
237-
upper_bounds = np.array([np.inf] * len(params))
243+
if propagate_none:
244+
upper_bounds = None
245+
else:
246+
upper_bounds = _fast_full_array(len(params), value=np.inf)
238247
else:
239248
upper_bounds = bounds.upper.astype(float)
240249

241-
if (lower_bounds > upper_bounds).any():
250+
if (
251+
lower_bounds is not None
252+
and upper_bounds is not None
253+
and (lower_bounds > upper_bounds).any()
254+
):
242255
msg = "Invalid bounds. Some lower bounds are larger than upper bounds."
243256
raise InvalidBoundsError(msg)
244257

245258
return lower_bounds, upper_bounds
259+
260+
261+
def _fast_full_array(length: int, value: float) -> NDArray[np.float64]:
262+
if length < 18:
263+
return np.array([value] * length)
264+
else:
265+
return np.full(length, value)

tests/optimagic/parameters/test_bounds.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
from numpy.testing import assert_array_equal
55

66
from optimagic.exceptions import InvalidBoundsError
7-
from optimagic.parameters.bounds import Bounds, get_internal_bounds, pre_process_bounds
7+
from optimagic.parameters.bounds import (
8+
Bounds,
9+
_get_fast_path_bounds,
10+
get_internal_bounds,
11+
pre_process_bounds,
12+
)
813

914

1015
@pytest.fixture()
@@ -132,3 +137,23 @@ def test_get_bounds_numpy_error(array_params):
132137
array_params,
133138
bounds=bounds,
134139
)
140+
141+
142+
def test_get_fast_path_bounds_none_propagate_true():
143+
got_lower, got_upper = _get_fast_path_bounds(
144+
params=np.array([1, 2, 3]),
145+
bounds=Bounds(lower=None, upper=None),
146+
propagate_none=True,
147+
)
148+
assert got_lower is None
149+
assert got_upper is None
150+
151+
152+
def test_get_fast_path_bounds_none_propagate_false():
153+
got_lower, got_upper = _get_fast_path_bounds(
154+
params=np.array([1, 2, 3]),
155+
bounds=Bounds(lower=None, upper=None),
156+
propagate_none=False,
157+
)
158+
assert_array_equal(got_lower, np.full(3, -np.inf))
159+
assert_array_equal(got_upper, np.full(3, np.inf))

0 commit comments

Comments
 (0)