Skip to content

Commit 89ebcdd

Browse files
committed
Go through test requirements and make sure they all apply to the
appropriate backends Also adjust the tests to reflect the new requirements. The test_miscellaneous and test_end_to_end were removed. Test_miscellaneous was removed because it doesn't cleanly fit into the requirements at this time, and while we need it to improve coverage, this branch is big enough without concerning ourselves with coverage. Getting good coverage will have to be done as a later effort. test_end_to_end was removed because in terms of functionality it's redundant with some of the tests in test_basic_functionality and test_options, and while it does provide some coverage that those don't, specifically on the _project function, coverage efforts will have to come later for sake of appropriate scoping, as mentioned above.
1 parent 2976d53 commit 89ebcdd

9 files changed

Lines changed: 172 additions & 241 deletions

python/prima/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,8 +489,19 @@ def fixed_nlc_fun(x):
489489
elif options['backend'].lower() == "python":
490490
from .backends.pyprima.cobyla.cobyla import cobyla
491491
from .backends.pyprima.common.infos import SMALL_TR_RADIUS
492+
if x0_is_scalar:
493+
x0 = np.array([x0])
494+
if args and x0_is_scalar:
495+
original_fun = fun
496+
fun = lambda x: original_fun(x[0], args)
497+
elif args:
498+
original_fun = fun
499+
fun = lambda x: original_fun(x, args)
500+
elif x0_is_scalar:
501+
original_fun = fun
502+
fun = lambda x: original_fun(x[0])
492503
def calcfc(x):
493-
f = fun(x, *args)
504+
f = fun(x)
494505
if nonlinear_constraint_function is not None:
495506
nlconstr = nonlinear_constraint_function(x)
496507
else:
@@ -512,6 +523,8 @@ def calcfc(x):
512523
callback=callback,
513524
**options
514525
)
526+
if x0_is_scalar:
527+
result.x = result.x[0]
515528
result = OptimizeResult(
516529
x = result.x,
517530
success = result.info == SMALL_TR_RADIUS,

python/tests/README.md

Lines changed: 76 additions & 63 deletions
Large diffs are not rendered by default.

python/tests/pyprima/test_end_to_end.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

python/tests/test_anonymous_lambda.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55

66
def test_anonymous_lambda():
77
'''
8-
In previous iterations of these bindings, memory was not handled correctly in C++ and using anonymous lambdas
8+
In previous iterations of the bindings, memory was not handled correctly in C++ and using anonymous lambdas
99
would cause issues including, but not limited to, segfaults and infinite hangs. This test is to ensure that
1010
anonymous lambdas can be used without issue.
1111
'''
1212
myNLC = NLC(lambda x: x[0]**2 - 9, [-np.inf], [0])
13-
res = minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='COBYLA', constraints=myNLC, callback=lambda x, *args: print(x))
13+
res = minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='COBYLA', constraints=myNLC,
14+
callback=lambda x, *args: print(x), options={'backend': 'Fortran'})
1415
assert abs(res.x[0] - 3) < 1e-2 and abs(res.x[1] - 4) < 1e-2 and abs(res.fun - 4) < 1e-2
1516

1617

@@ -22,5 +23,6 @@ def test_anonymous_lambda_unclean_exit():
2223
'''
2324
myNLC = NLC(lambda x: x[0]**2 - 9, [-np.inf], [0])
2425
with pytest.raises(ValueError) as e_info:
25-
minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='GARBAGE', constraints=myNLC, callback=lambda x, *args: print(x))
26+
minimize(lambda x: (x[0] - 5)**2 + (x[1] - 4)**2, [0.0] * 2, method='GARBAGE', constraints=myNLC,
27+
callback=lambda x, *args: print(x), options={'backend': 'Fortran'})
2628
assert e_info.match("Method must be one of NEWUOA, UOBYQA, BOBYQA, COBYLA, or LINCOA, not 'garbage'")

python/tests/test_basic_functionality.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from objective import fun
55

66

7-
def test_provide_nonlinear_constraints_alone(backend_fixture):
7+
def test_provide_nonlinear_constraints_alone(pyprima_turn_on_debugging, backend_fixture):
88
nlc = NLC(lambda x: np.array([x[0]**2, x[1]**2]), lb=[25]*2, ub=[100]*2)
99
x0 = [0, 0]
1010
res = minimize(fun, x0, constraints=nlc, options={'backend': backend_fixture})
@@ -79,4 +79,11 @@ def test_unconstrained_and_select_any_algorithm(method):
7979
def test_invalid_backend():
8080
x0 = [0, 0]
8181
with pytest.raises(ValueError, match="Backend must be either 'Fortran' or 'Python', not 'InvalidBackend'"):
82-
minimize(fun, x0, options={'backend': 'InvalidBackend'})
82+
minimize(fun, x0, options={'backend': 'InvalidBackend'})
83+
84+
85+
def test_select_algorithm_not_provided_by_python_implementation():
86+
x0 = [0, 0]
87+
with pytest.warns(UserWarning, match="The pure Python implementation only supports COBYLA at this time. The Fortran implementation will be used instead."):
88+
res = minimize(fun, x0, method='newuoa', options={'backend': 'Python'})
89+
assert fun.result_point_and_value_are_optimal(res)

python/tests/test_combining_constraints.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from prima import minimize as prima_minimize, NonlinearConstraint as prima_NLC, LinearConstraint as prima_LC, Bounds as prima_Bounds
22
import numpy as np
33
from objective import fun
4-
import pytest
54

65

76
def test_providing_linear_and_nonlinear_constraints(backend_fixture):
@@ -15,15 +14,11 @@ def test_providing_linear_and_nonlinear_constraints(backend_fixture):
1514
assert res.method == "cobyla"
1615

1716

18-
def test_providing_bounds_and_linear_constraints(backend_fixture):
17+
def test_providing_bounds_and_linear_constraints():
1918
lc = prima_LC(np.array([1,1]), lb=10, ub=15)
2019
bounds = prima_Bounds(1, 1)
2120
x0 = [0, 0]
22-
if backend_fixture == 'Fortran':
23-
res = prima_minimize(fun, x0, constraints=lc, bounds=bounds, options={'backend': backend_fixture})
24-
elif backend_fixture == "Python":
25-
with pytest.warns(UserWarning, match="The pure Python implementation only supports COBYLA at this time. The Fortran implementation will be used instead."):
26-
res = prima_minimize(fun, x0, constraints=lc, bounds=bounds, options={'backend': backend_fixture})
21+
res = prima_minimize(fun, x0, constraints=lc, bounds=bounds)
2722
assert np.isclose(res.x[0], 1, atol=1e-6, rtol=1e-6)
2823
assert np.isclose(res.x[1], 9, atol=1e-6, rtol=1e-6)
2924
assert np.isclose(res.fun, 41, atol=1e-6, rtol=1e-6)

python/tests/test_data_types.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,94 +4,98 @@
44
import pytest
55

66

7-
def test_x0_as_list():
7+
def test_x0_as_list(backend_fixture):
88
x0 = [0.0] * 2
9-
res = minimize(fun, x0)
9+
method = 'COBYLA' if backend_fixture == 'Python' else None
10+
res = minimize(fun, x0, method=method, options={'backend': backend_fixture})
1011
assert fun.result_point_and_value_are_optimal(res)
1112

1213

13-
def test_x0_as_array():
14+
def test_x0_as_array(backend_fixture):
1415
x0 = np.array([0.0] * 2)
15-
res = minimize(fun, x0)
16+
method = 'COBYLA' if backend_fixture == 'Python' else None
17+
res = minimize(fun, x0, method=method, options={'backend': backend_fixture})
1618
assert fun.result_point_and_value_are_optimal(res)
1719

1820

19-
def test_x0_as_scalar():
21+
def test_x0_as_scalar(backend_fixture):
2022
x0 = 0.0
2123
# We need a custom function since the default objective function we're
2224
# using for tests expects something that can be unpacked into two variables.
23-
res = minimize(lambda x: (x-5)**2, x0)
25+
method = 'COBYLA' if backend_fixture == 'Python' else None
26+
res = minimize(lambda x: (x-5)**2, x0, method=method, options={'backend': backend_fixture})
2427
assert np.isclose(res.x, 5.0, rtol=1e-6)
2528
assert np.isclose(res.fun, 0.0, rtol=1e-6)
2629

2730

28-
def test_constraint_function_returns_numpy_array():
31+
def test_constraint_function_returns_numpy_array(backend_fixture):
2932
nlc = NLC(lambda x: np.array([x[0], x[1]]), lb=[-np.inf]*2, ub=[10]*2)
3033
x0 = [0, 0]
31-
res = minimize(fun, x0, method='COBYLA', constraints=nlc)
34+
res = minimize(fun, x0, method='COBYLA', constraints=nlc, options={'backend': backend_fixture})
3235
assert fun.result_point_and_value_are_optimal(res)
3336

3437

35-
def test_constraint_function_returns_list():
38+
def test_constraint_function_returns_list(backend_fixture):
3639
nlc = NLC(lambda x: [x[0], x[1]], lb=[-np.inf]*2, ub=[10]*2)
3740
x0 = [0, 0]
38-
res = minimize(fun, x0, method='COBYLA', constraints=nlc)
41+
res = minimize(fun, x0, method='COBYLA', constraints=nlc, options={'backend': backend_fixture})
3942
assert fun.result_point_and_value_are_optimal(res)
4043

4144

42-
def test_constraint_function_returns_scalar():
45+
def test_constraint_function_returns_scalar(backend_fixture):
4346
nlc = NLC(lambda x: float(np.linalg.norm(x) - np.linalg.norm(fun.optimal_x)), lb=[-np.inf], ub=[0])
4447
x0 = [0, 0]
45-
res = minimize(fun, x0, method='COBYLA', constraints=nlc)
48+
res = minimize(fun, x0, method='COBYLA', constraints=nlc, options={'backend': backend_fixture})
4649
assert fun.result_point_and_value_are_optimal(res)
4750

4851

4952

5053
@pytest.mark.parametrize("A", (1, [1], np.array([1])))
5154
@pytest.mark.parametrize("lb", (0, [0], np.array([0])))
5255
@pytest.mark.parametrize("ub", (4, [4], np.array([4])))
53-
def test_linear_constraint_data_types(A, lb, ub):
56+
def test_linear_constraint_data_types(A, lb, ub, backend_fixture):
5457
myLC = LC(A=A, lb=lb, ub=ub)
5558
# If A is scalar, x must have dimension 1, so we need a univariate function for that
5659
scalar_fun = lambda x: (x - 5)**2
5760
x0 = 0
58-
res = minimize(scalar_fun, x0, method='LINCOA', constraints=myLC)
61+
method = 'COBYLA' if backend_fixture == 'Python' else None
62+
res = minimize(scalar_fun, x0, constraints=myLC, method=method, options={'backend': backend_fixture})
5963
assert np.isclose(res.x, 4, rtol=1e-2)
6064

6165

62-
def test_nonlinear_constraint_lb_scalar_ub_scalar():
66+
def test_nonlinear_constraint_lb_scalar_ub_scalar(backend_fixture):
6367
nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=25, ub=100)
6468
x0 = [0, 0]
65-
res = minimize(fun, x0, constraints=nlc)
69+
res = minimize(fun, x0, constraints=nlc, options={'backend': backend_fixture})
6670
assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
6771
assert np.isclose(res.x[1], 5, atol=1e-6, rtol=1e-6)
6872
assert np.isclose(res.fun, 1, atol=1e-6, rtol=1e-6)
6973

7074

71-
def test_nonlinear_constraint_lb_scalar_ub_not_scalar():
75+
def test_nonlinear_constraint_lb_scalar_ub_not_scalar(backend_fixture):
7276
nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=9, ub=[9, 16])
7377
x0 = [0, 0]
74-
res = minimize(fun, x0, constraints=nlc)
78+
res = minimize(fun, x0, constraints=nlc, options={'backend': backend_fixture})
7579
assert np.isclose(res.x[0], 3, atol=1e-6, rtol=1e-6)
7680
# cp38-manylinux_i686 didn't quite get to 4 with a tol of e-6,
7781
# so we lower the tolerance a little bit.
7882
assert np.isclose(res.x[1], 4, atol=1e-5, rtol=1e-5)
7983
assert np.isclose(res.fun, 4, atol=1e-6, rtol=1e-6)
8084

8185

82-
def test_nonlinear_constraint_lb_not_scalar_ub_scalar():
86+
def test_nonlinear_constraint_lb_not_scalar_ub_scalar(backend_fixture):
8387
nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=[25, 36], ub=100)
8488
x0 = [0, 0]
85-
res = minimize(fun, x0, constraints=nlc)
89+
res = minimize(fun, x0, constraints=nlc, options={'backend': backend_fixture})
8690
assert np.isclose(res.x[0], 5, atol=1e-6, rtol=1e-6)
8791
assert np.isclose(res.x[1], 6, atol=1e-6, rtol=1e-6)
8892
assert np.isclose(res.fun, 4, atol=1e-6, rtol=1e-6)
8993

9094

91-
def test_nonlinear_constraint_lb_not_scalar_ub_not_scalar():
95+
def test_nonlinear_constraint_lb_not_scalar_ub_not_scalar(backend_fixture):
9296
nlc = NLC(lambda x: [x[0]**2, x[1]**2], lb=[4, 4], ub=[4, 9])
9397
x0 = [0, 0]
94-
res = minimize(fun, x0, constraints=nlc)
98+
res = minimize(fun, x0, constraints=nlc, options={'backend': backend_fixture})
9599
assert np.isclose(res.x[0], 2, atol=1e-6, rtol=1e-6)
96100
assert np.isclose(res.x[1], 3, atol=1e-6, rtol=1e-6)
97101
assert np.isclose(res.fun, 10, atol=1e-6, rtol=1e-6)

python/tests/test_miscellaneous.py

Lines changed: 0 additions & 97 deletions
This file was deleted.

0 commit comments

Comments
 (0)