File size: 5,272 Bytes
7885a28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import math

import numpy as np
from numpy.testing import assert_allclose, assert_, assert_array_equal
import pytest

from scipy.optimize import fmin_cobyla, minimize, Bounds


class TestCobyla:
    def setup_method(self):
        self.x0 = [4.95, 0.66]
        self.solution = [math.sqrt(25 - (2.0/3)**2), 2.0/3]
        self.opts = {'disp': False, 'rhobeg': 1, 'tol': 1e-5,
                     'maxiter': 100}

    def fun(self, x):
        return x[0]**2 + abs(x[1])**3

    def con1(self, x):
        return x[0]**2 + x[1]**2 - 25

    def con2(self, x):
        return -self.con1(x)

    @pytest.mark.xslow(True, reason='not slow, but noisy so only run rarely')
    def test_simple(self, capfd):
        # use disp=True as smoke test for gh-8118
        x = fmin_cobyla(self.fun, self.x0, [self.con1, self.con2], rhobeg=1,
                        rhoend=1e-5, maxfun=100, disp=True)
        assert_allclose(x, self.solution, atol=1e-4)

    def test_minimize_simple(self):
        class Callback:
            def __init__(self):
                self.n_calls = 0
                self.last_x = None

            def __call__(self, x):
                self.n_calls += 1
                self.last_x = x

        callback = Callback()

        # Minimize with method='COBYLA'
        cons = ({'type': 'ineq', 'fun': self.con1},
                {'type': 'ineq', 'fun': self.con2})
        sol = minimize(self.fun, self.x0, method='cobyla', constraints=cons,
                       callback=callback, options=self.opts)
        assert_allclose(sol.x, self.solution, atol=1e-4)
        assert_(sol.success, sol.message)
        assert_(sol.maxcv < 1e-5, sol)
        assert_(sol.nfev < 70, sol)
        assert_(sol.fun < self.fun(self.solution) + 1e-3, sol)
        assert_(sol.nfev == callback.n_calls,
                "Callback is not called exactly once for every function eval.")
        assert_array_equal(
            sol.x,
            callback.last_x,
            "Last design vector sent to the callback is not equal to returned value.",
        )

    def test_minimize_constraint_violation(self):
        rng = np.random.RandomState(1234)
        pb = rng.rand(10, 10)
        spread = rng.rand(10)

        def p(w):
            return pb.dot(w)

        def f(w):
            return -(w * spread).sum()

        def c1(w):
            return 500 - abs(p(w)).sum()

        def c2(w):
            return 5 - abs(p(w).sum())

        def c3(w):
            return 5 - abs(p(w)).max()

        cons = ({'type': 'ineq', 'fun': c1},
                {'type': 'ineq', 'fun': c2},
                {'type': 'ineq', 'fun': c3})
        w0 = np.zeros((10,))
        sol = minimize(f, w0, method='cobyla', constraints=cons,
                       options={'catol': 1e-6})
        assert_(sol.maxcv > 1e-6)
        assert_(not sol.success)


def test_vector_constraints():
    # test that fmin_cobyla and minimize can take a combination
    # of constraints, some returning a number and others an array
    def fun(x):
        return (x[0] - 1)**2 + (x[1] - 2.5)**2

    def fmin(x):
        return fun(x) - 1

    def cons1(x):
        a = np.array([[1, -2, 2], [-1, -2, 6], [-1, 2, 2]])
        return np.array([a[i, 0] * x[0] + a[i, 1] * x[1] +
                         a[i, 2] for i in range(len(a))])

    def cons2(x):
        return x     # identity, acts as bounds x > 0

    x0 = np.array([2, 0])
    cons_list = [fun, cons1, cons2]

    xsol = [1.4, 1.7]
    fsol = 0.8

    # testing fmin_cobyla
    sol = fmin_cobyla(fun, x0, cons_list, rhoend=1e-5)
    assert_allclose(sol, xsol, atol=1e-4)

    sol = fmin_cobyla(fun, x0, fmin, rhoend=1e-5)
    assert_allclose(fun(sol), 1, atol=1e-4)

    # testing minimize
    constraints = [{'type': 'ineq', 'fun': cons} for cons in cons_list]
    sol = minimize(fun, x0, constraints=constraints, tol=1e-5)
    assert_allclose(sol.x, xsol, atol=1e-4)
    assert_(sol.success, sol.message)
    assert_allclose(sol.fun, fsol, atol=1e-4)

    constraints = {'type': 'ineq', 'fun': fmin}
    sol = minimize(fun, x0, constraints=constraints, tol=1e-5)
    assert_allclose(sol.fun, 1, atol=1e-4)


class TestBounds:
    # Test cobyla support for bounds (only when used via `minimize`)
    # Invalid bounds is tested in
    # test_optimize.TestOptimizeSimple.test_minimize_invalid_bounds

    def test_basic(self):
        def f(x):
            return np.sum(x**2)

        lb = [-1, None, 1, None, -0.5]
        ub = [-0.5, -0.5, None, None, -0.5]
        bounds = [(a, b) for a, b in zip(lb, ub)]
        # these are converted to Bounds internally

        res = minimize(f, x0=[1, 2, 3, 4, 5], method='cobyla', bounds=bounds)
        ref = [-0.5, -0.5, 1, 0, -0.5]
        assert res.success
        assert_allclose(res.x, ref, atol=1e-3)

    def test_unbounded(self):
        def f(x):
            return np.sum(x**2)

        bounds = Bounds([-np.inf, -np.inf], [np.inf, np.inf])
        res = minimize(f, x0=[1, 2], method='cobyla', bounds=bounds)
        assert res.success
        assert_allclose(res.x, 0, atol=1e-3)

        bounds = Bounds([1, -np.inf], [np.inf, np.inf])
        res = minimize(f, x0=[1, 2], method='cobyla', bounds=bounds)
        assert res.success
        assert_allclose(res.x, [1, 0], atol=1e-3)