1
- # copyright: hyperactive developers, MIT License (see LICENSE file)
2
-
3
- # todo: write an informative docstring for the file or module, remove the above
4
-
5
- __author__ = ["SimonBlanke" , "yashnator" ]
6
-
7
1
from ..base_optimizer import BaseOptimizer
8
2
9
3
import numpy as np
@@ -57,15 +51,58 @@ class COBYLA(BaseOptimizer):
57
51
... )
58
52
>>> opt.search(sphere_function, n_iter=10)
59
53
"""
54
+
55
+ name = "COBYLA"
56
+ _name_ = "COBYLA"
57
+ __name__ = "COBYLA"
60
58
61
- _tags = {
62
- "authors" : ["SimonBlanke" , "yashnator" ],
63
- }
59
+ optimizer_type = "local"
60
+ computationally_expensive = False
61
+
62
+ def __init__ (
63
+ self ,
64
+ search_space ,
65
+ x_0 : np .array ,
66
+ rho_beg : int ,
67
+ rho_end : int ,
68
+ initialize = {"grid" : 4 , "random" : 2 , "vertices" : 4 },
69
+ constraints = [],
70
+ random_state = None ,
71
+ rand_rest_p = 0 ,
72
+ nth_process = None ,
73
+ alpha = 0.25 ,
74
+ beta = 2.1 ,
75
+ ):
76
+ super ().__init__ (
77
+ search_space = search_space ,
78
+ initialize = initialize ,
79
+ constraints = constraints ,
80
+ random_state = random_state ,
81
+ rand_rest_p = rand_rest_p ,
82
+ nth_process = nth_process ,
83
+ )
84
+ self .dim = np .shape (x_0 )[0 ]
85
+ self .simplex = self ._generate_initial_simplex (x_0 , rho_beg )
86
+ self .rho_beg = rho_beg
87
+ self .rho_end = rho_end
88
+ self ._rho = rho_beg
89
+
90
+ self ._mu = 0
91
+ self .state = 0
92
+ self .FLAG = 0
93
+
94
+ if alpha <= 0 or alpha >= 1 :
95
+ raise ValueError ("Parameter alpha must belong to the range (0, 1)" )
96
+
97
+ if beta <= 1 :
98
+ raise ValueError ("Parameter beta must belong to the range (1, ∞)" )
99
+
100
+ self .alpha = alpha
101
+ self .beta = beta
64
102
65
103
def _generate_initial_simplex (self , x_0_initial , rho_beg ):
66
104
n = x_0_initial .shape [0 ]
67
105
arr = np .ones ((n + 1 , 1 )) * x_0_initial + rho_beg * np .eye (n + 1 , n )
68
- print (arr )
69
106
return arr
70
107
71
108
def _vertices_to_oppsite_face_distances (self ):
@@ -76,9 +113,6 @@ def _vertices_to_oppsite_face_distances(self):
76
113
For each vertex, the opposite hyperplane is obtained after removing the current
77
114
vertex and then finding the projection on the subspace spanned by the hyperplane.
78
115
The distance is then the L2 norm between projection and the current vertex.
79
-
80
- Args:
81
- self: instance of current COBYLA class
82
116
83
117
Returns:
84
118
distances: (n+1,) array of distances from each vertex to its opposite face.
@@ -96,66 +130,65 @@ def _vertices_to_oppsite_face_distances(self):
96
130
return distances
97
131
98
132
def _is_simplex_acceptable (self ):
133
+ """
134
+ Determine whether the current simplex is acceptable according to COBYLA criteria.
135
+
136
+ In COBYLA, a simplex is acceptable if:
137
+ 1. The distances between each vertex and the first vertex (η_j) do not exceed
138
+ a threshold defined by `beta * rho`, controlling simplex edge lengths.
139
+ 2. The distance from each vertex to its opposite (n-1)-dimensional face (σ_j)
140
+ is not too small, ensuring the simplex is well-shaped. These distances must
141
+ exceed a threshold given by `alpha * rho`.
142
+
143
+ This function enforces these two geometric conditions to ensure that the simplex
144
+ maintains good numerical stability and approximation quality.
145
+
146
+ Returns:
147
+ bool: True if both η and σ conditions are satisfied, False otherwise.
148
+ """
99
149
eta = [np .linalg .norm (x - self .simplex [0 ]) for x in self .simplex ]
100
150
eta_constraint = self .beta * self ._rho
101
151
for eta_j in eta :
102
152
if eta_j > eta_constraint :
103
153
return False
104
154
sigma = self ._vertices_to_oppsite_face_distances ()
105
155
sigma_constraint = self .alpha * self ._rho
106
- print (sigma )
107
156
for sigma_j in sigma :
108
157
if sigma_j < sigma_constraint :
109
158
return False
110
159
return True
111
160
112
161
def _eval_constraints (self , pos ):
113
- # TODO: evalute constraints in optimized way
114
-
115
- return None
162
+ """
163
+ Evaluates constraints for the given position
164
+
165
+ Returns:
166
+ np.array: array containing the evaluated value of constraints
167
+ """
168
+ return [np .clip (f (pos ), 0 , np .inf ) for f in self .constraints ]
116
169
117
- def _merit_value (self , pos ):
118
- # TODO: write the merit function using the _eval_constraints
119
- return 0
170
+ def _phi (self , pos ):
171
+ """
172
+ Compute the merit function Φ used in COBYLA to evaluate candidate points. Given by:
120
173
121
- def __init__ (
122
- self ,
123
- search_space ,
124
- x_0 : np .array ,
125
- rho_beg : int ,
126
- rho_end : int ,
127
- initialize = {"grid" : 4 , "random" : 2 , "vertices" : 4 },
128
- constraints = [],
129
- random_state = None ,
130
- rand_rest_p = 0 ,
131
- nth_process = None ,
132
- alpha = 0.25 ,
133
- beta = 2.1
134
- ):
135
- super ().__init__ (
136
- search_space = search_space ,
137
- initialize = initialize ,
138
- constraints = constraints ,
139
- random_state = random_state ,
140
- rand_rest_p = rand_rest_p ,
141
- nth_process = nth_process ,
142
- )
143
- self .dim = np .shape (x_0 )[0 ]
144
- self .simplex = self ._generate_initial_simplex (x_0 , rho_beg )
145
- self .rho_beg = rho_beg
146
- self .rho_end = rho_end
147
- self ._rho = rho_beg
148
- self .state = 0
149
- self .FLAG = 0
150
-
151
- if alpha <= 0 or alpha >= 1 :
152
- raise ValueError ("Parameter alpha must belong to the range (0, 1)" )
153
-
154
- if beta <= 1 :
155
- raise ValueError ("Parameter beta must belong to the range (1, ∞)" )
156
-
157
- self .alpha = alpha
158
- self .beta = beta
174
+ Φ(x) = f(x) + μ * max_i(max(c_i(x), 0))
175
+
176
+ Args:
177
+ pos (np.array): The point in parameter space at which to evaluate the merit function.
178
+
179
+ Returns:
180
+ float: The value of the merit function Φ at the given point.
181
+ """
182
+ c = self ._eval_constraints (pos )
183
+ return self .objective_function (pos ) + self ._mu * np .max (c )
184
+
185
+ def _rearrange_optimum_to_top (self ):
186
+ """
187
+ Rearrages simplex vertices such that first row is the optimal position
188
+ """
189
+ opt_idx = np .argmin ([self ._phi (vert ) for vert in self .simplex ])
190
+ if opt_idx != 0 :
191
+ self .simplex [[0 , opt_idx ]] = self .simplex [[opt_idx , 0 ]]
159
192
160
193
def iterate (self ):
161
194
# TODO: Impl
@@ -167,7 +200,7 @@ def _score(self, pos):
167
200
168
201
def evaluate (self , pos ):
169
202
# TODO: Impl
170
- return self ._merit_value ( pos )
203
+ return self ._phi ( self . simplex [ 0 ] )
171
204
172
205
def finish_search (self ):
173
206
# TODO: Impl
0 commit comments