Skip to content

Commit f278f6d

Browse files
authored
Fix that BoFire is not working without cyipopt (#522)
* fix imports * fix more * more exclusions * remove warnings
1 parent 8c91da4 commit f278f6d

File tree

6 files changed

+63
-22
lines changed

6 files changed

+63
-22
lines changed

.github/workflows/test.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,32 @@ jobs:
3838
- name: Run data model tests
3939
run: pytest tests/bofire/data_models
4040

41+
testing_optimization_only:
42+
runs-on: ubuntu-latest
43+
strategy:
44+
matrix:
45+
python-version: [ '3.12' ]
46+
steps:
47+
- name: Check out repo
48+
uses: actions/checkout@v4
49+
50+
- name: Set up Python
51+
uses: actions/setup-python@v5
52+
with:
53+
python-version: ${{ matrix.python-version }}
54+
55+
- name: Set up uv
56+
uses: astral-sh/setup-uv@v2
57+
with:
58+
enable-cache: true
59+
60+
- name: Install Dependencies
61+
run: uv pip install ".[optimization, tests]" --system
62+
63+
- name: Run tests
64+
shell: bash -l {0}
65+
run: pytest -ra --cov=bofire --cov-report term-missing tests
66+
4167
testing:
4268
runs-on: ubuntu-latest
4369
strategy:

bofire/strategies/doe/design.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,10 @@ def find_local_max_ipopt(
6161
# warn user if IPOPT scipy interface is not available
6262
try:
6363
from cyipopt import minimize_ipopt # type: ignore
64-
except ImportError as e:
65-
warnings.warn(e.msg)
66-
warnings.warn(
67-
"please run `conda install -c conda-forge cyipopt` for this functionality.",
64+
except ImportError:
65+
raise ImportError(
66+
"cyipopt is not installed. Install it via `conda install -c conda-forge cyipopt`"
6867
)
69-
raise e
7068

7169
objective_function = get_objective_function(
7270
criterion, domain=domain, n_experiments=n_experiments

bofire/strategies/doe/objective.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import numpy as np
88
import pandas as pd
99
import torch
10-
from cyipopt import minimize_ipopt # type: ignore
1110
from formulaic import Formula
1211
from scipy.optimize._minimize import standardize_constraints
1312
from torch import Tensor
@@ -204,6 +203,13 @@ def __init__(
204203
If None is provided, the default options (maxiter = 500) are used.
205204
"""
206205

206+
try:
207+
from cyipopt import minimize_ipopt # type: ignore
208+
except ImportError:
209+
raise ImportError(
210+
"cyipopt is not installed. Install it via `conda install -c conda-forge cyipopt`"
211+
)
212+
207213
if transform_range is not None:
208214
raise ValueError(
209215
"IOptimality does not support transformations of the input variables."

tests/bofire/strategies/doe/test_objective.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import importlib
2+
13
import numpy as np
24
import pandas as pd
35
import pytest
@@ -30,12 +32,15 @@
3032
from bofire.strategies.doe.utils import get_formula_from_string
3133

3234

35+
CYIPOPT_AVAILABLE = importlib.util.find_spec("cyipopt") is not None
36+
37+
3338
def test_Objective_model_jacobian_t():
3439
# "small" model
3540
domain = Domain.from_lists(
3641
inputs=[
3742
ContinuousInput(
38-
key=f"x{i+1}",
43+
key=f"x{i + 1}",
3944
bounds=(0, 1),
4045
)
4146
for i in range(3)
@@ -98,7 +103,7 @@ def test_Objective_model_jacobian_t():
98103
domain = Domain.from_lists(
99104
inputs=[
100105
ContinuousInput(
101-
key=f"x{i+1}",
106+
key=f"x{i + 1}",
102107
bounds=(0, 1),
103108
)
104109
for i in range(5)
@@ -383,7 +388,7 @@ def test_Objective_convert_input_to_model_tensor():
383388
domain = Domain.from_lists(
384389
inputs=[
385390
ContinuousInput(
386-
key=f"x{i+1}",
391+
key=f"x{i + 1}",
387392
bounds=(0, 1),
388393
)
389394
for i in range(3)
@@ -404,7 +409,7 @@ def test_DOptimality_instantiation():
404409
domain = Domain.from_lists(
405410
inputs=[
406411
ContinuousInput(
407-
key=f"x{i+1}",
412+
key=f"x{i + 1}",
408413
bounds=(0, 1),
409414
)
410415
for i in range(3)
@@ -451,7 +456,7 @@ def test_DOptimality_instantiation():
451456
domain = Domain.from_lists(
452457
inputs=[
453458
ContinuousInput(
454-
key=f"x{i+1}",
459+
key=f"x{i + 1}",
455460
bounds=(0, 1),
456461
)
457462
for i in range(3)
@@ -487,7 +492,7 @@ def get_jacobian(x: np.ndarray, delta=1e-3) -> np.ndarray: # type: ignore
487492
domain = Domain.from_lists(
488493
inputs=[
489494
ContinuousInput(
490-
key=f"x{i+1}",
495+
key=f"x{i + 1}",
491496
bounds=(0, 1),
492497
)
493498
for i in range(2)
@@ -642,7 +647,7 @@ def test_DOptimality_evaluate():
642647
domain = Domain.from_lists(
643648
inputs=[
644649
ContinuousInput(
645-
key=f"x{i+1}",
650+
key=f"x{i + 1}",
646651
bounds=(0, 1),
647652
)
648653
for i in range(3)
@@ -660,7 +665,7 @@ def test_AOptimality_evaluate():
660665
domain = Domain.from_lists(
661666
inputs=[
662667
ContinuousInput(
663-
key=f"x{i+1}",
668+
key=f"x{i + 1}",
664669
bounds=(0, 1),
665670
)
666671
for i in range(3)
@@ -803,6 +808,7 @@ def test_SpaceFilling_evaluate_jacobian():
803808
assert np.allclose(space_filling.evaluate_jacobian(x), [-1, -1, 2, 0])
804809

805810

811+
@pytest.mark.skipif(not CYIPOPT_AVAILABLE, reason="requires cyipopt")
806812
def test_MinMaxTransform():
807813
domain = Domain.from_lists(
808814
inputs=[ContinuousInput(key="x1", bounds=(0, 1))],
@@ -888,6 +894,7 @@ def test_MinMaxTransform():
888894
)
889895

890896

897+
@pytest.mark.skipif(not CYIPOPT_AVAILABLE, reason="requires cyipopt")
891898
def test_IOptimality_instantiation():
892899
# no constraints
893900
domain = Domain.from_lists(
@@ -906,7 +913,7 @@ def test_IOptimality_instantiation():
906913

907914
# inequality constraints
908915
domain = Domain.from_lists(
909-
inputs=[ContinuousInput(key=f"x{i+1}", bounds=(0, 1)) for i in range(2)],
916+
inputs=[ContinuousInput(key=f"x{i + 1}", bounds=(0, 1)) for i in range(2)],
910917
outputs=[ContinuousOutput(key="y")],
911918
constraints=[
912919
LinearInequalityConstraint(
@@ -930,7 +937,7 @@ def test_IOptimality_instantiation():
930937

931938
# equality constraints
932939
domain = Domain.from_lists(
933-
inputs=[ContinuousInput(key=f"x{i+1}", bounds=(0, 1)) for i in range(2)],
940+
inputs=[ContinuousInput(key=f"x{i + 1}", bounds=(0, 1)) for i in range(2)],
934941
outputs=[ContinuousOutput(key="y")],
935942
constraints=[
936943
LinearEqualityConstraint(features=["x1", "x2"], coefficients=[1, 1], rhs=1)

tests/bofire/surrogates/test_gps.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_SingleTaskGPModel(kernel, scaler, output_scaler):
7979
inputs = Inputs(
8080
features=[
8181
ContinuousInput(
82-
key=f"x_{i+1}",
82+
key=f"x_{i + 1}",
8383
bounds=(-4, 4),
8484
)
8585
for i in range(2)
@@ -136,6 +136,7 @@ def test_SingleTaskGPModel(kernel, scaler, output_scaler):
136136
assert_frame_equal(preds, preds2)
137137

138138

139+
@pytest.mark.skipif(not RDKIT_AVAILABLE, reason="requires rdkit")
139140
@pytest.mark.parametrize(
140141
"kernel, scaler, output_scaler",
141142
[
@@ -336,6 +337,7 @@ def test_SingleTaskGPModel_feature_subsets():
336337
assert gp_mapped.model.covar_module.kernels[1].active_dims.tolist() == [2, 3, 4, 5]
337338

338339

340+
@pytest.mark.skipif(not RDKIT_AVAILABLE, reason="requires rdkit")
339341
def test_SingleTaskGPModel_mixed_features():
340342
"""test that we can use a single task gp with mixed features"""
341343
inputs = Inputs(
@@ -400,7 +402,7 @@ def test_MixedSingleTaskGPHyperconfig():
400402
inputs = Inputs(
401403
features=[
402404
ContinuousInput(
403-
key=f"x_{i+1}",
405+
key=f"x_{i + 1}",
404406
bounds=(-4, 4),
405407
)
406408
for i in range(2)
@@ -440,7 +442,7 @@ def test_MixedSingleTaskGPModel_invalid_preprocessing():
440442
inputs = Inputs(
441443
features=[
442444
ContinuousInput(
443-
key=f"x_{i+1}",
445+
key=f"x_{i + 1}",
444446
bounds=(-4, 4),
445447
)
446448
for i in range(2)
@@ -469,7 +471,7 @@ def test_MixedSingleTaskGPModel(kernel, scaler, output_scaler):
469471
inputs = Inputs(
470472
features=[
471473
ContinuousInput(
472-
key=f"x_{i+1}",
474+
key=f"x_{i + 1}",
473475
bounds=(-4, 4),
474476
)
475477
for i in range(2)
@@ -541,6 +543,7 @@ def test_MixedSingleTaskGPModel(kernel, scaler, output_scaler):
541543
assert_frame_equal(preds, preds2)
542544

543545

546+
@pytest.mark.skipif(not RDKIT_AVAILABLE, reason="requires rdkit")
544547
@pytest.mark.parametrize(
545548
"kernel, scaler, output_scaler",
546549
[

tests/bofire/surrogates/test_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def test_get_scaler(
133133
inputs = Inputs(
134134
features=[
135135
ContinuousInput(
136-
key=f"x_{i+1}",
136+
key=f"x_{i + 1}",
137137
bounds=(-4, 4),
138138
)
139139
for i in range(2)
@@ -171,6 +171,7 @@ def test_get_scaler(
171171
assert (scaler.coefficient == expected_coefficient).all()
172172

173173

174+
@pytest.mark.skipif(not RDKIT_AVAILABLE, reason="requires rdkit")
174175
@pytest.mark.parametrize(
175176
"scaler_enum, input_preprocessing_specs, expected_scaler, expected_indices",
176177
[
@@ -233,7 +234,7 @@ def test_get_scaler_molecular(
233234
inputs = Inputs(
234235
features=[
235236
ContinuousInput(
236-
key=f"x_{i+1}",
237+
key=f"x_{i + 1}",
237238
bounds=(0, 5),
238239
)
239240
for i in range(2)

0 commit comments

Comments
 (0)