Skip to content

Commit 2fd3c91

Browse files
committed
Feat: Feat: Add COBYLA optimizer (2 / n)
- Add merit function - Add docstrings
1 parent ba4ea47 commit 2fd3c91

File tree

2 files changed

+49
-14
lines changed

2 files changed

+49
-14
lines changed

src/gradient_free_optimizers/optimizers/core_optimizer/core_optimizer.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def __init__(
3737

3838
self.search_space = search_space
3939
self.initialize = initialize
40-
print(constraints)
4140
self.constraints = constraints
4241
self.random_state = random_state
4342
self.rand_rest_p = rand_rest_p

src/gradient_free_optimizers/optimizers/local_opt/COBYLA.py

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ class COBYLA(BaseOptimizer):
6565
def _generate_initial_simplex(self, x_0_initial, rho_beg):
6666
n = x_0_initial.shape[0]
6767
arr = np.ones((n + 1, 1)) * x_0_initial + rho_beg * np.eye(n + 1, n)
68-
print(arr)
6968
return arr
7069

7170
def _vertices_to_oppsite_face_distances(self):
@@ -76,9 +75,6 @@ def _vertices_to_oppsite_face_distances(self):
7675
For each vertex, the opposite hyperplane is obtained after removing the current
7776
vertex and then finding the projection on the subspace spanned by the hyperplane.
7877
The distance is then the L2 norm between projection and the current vertex.
79-
80-
Args:
81-
self: instance of current COBYLA class
8278
8379
Returns:
8480
distances: (n+1,) array of distances from each vertex to its opposite face.
@@ -96,27 +92,65 @@ def _vertices_to_oppsite_face_distances(self):
9692
return distances
9793

9894
def _is_simplex_acceptable(self):
95+
"""
96+
Determine whether the current simplex is acceptable according to COBYLA criteria.
97+
98+
In COBYLA, a simplex is acceptable if:
99+
1. The distances between each vertex and the first vertex (η_j) do not exceed
100+
a threshold defined by `beta * rho`, controlling simplex edge lengths.
101+
2. The distance from each vertex to its opposite (n-1)-dimensional face (σ_j)
102+
is not too small, ensuring the simplex is well-shaped. These distances must
103+
exceed a threshold given by `alpha * rho`.
104+
105+
This function enforces these two geometric conditions to ensure that the simplex
106+
maintains good numerical stability and approximation quality.
107+
108+
Returns:
109+
bool: True if both η and σ conditions are satisfied, False otherwise.
110+
"""
99111
eta = [np.linalg.norm(x - self.simplex[0]) for x in self.simplex]
100112
eta_constraint = self.beta * self._rho
101113
for eta_j in eta:
102114
if eta_j > eta_constraint:
103115
return False
104116
sigma = self._vertices_to_oppsite_face_distances()
105117
sigma_constraint = self.alpha * self._rho
106-
print(sigma)
107118
for sigma_j in sigma:
108119
if sigma_j < sigma_constraint:
109120
return False
110121
return True
111122

112123
def _eval_constraints(self, pos):
113-
# TODO: evalute constraints in optimized way
114-
115-
return None
124+
"""
125+
Evaluates constraints for the given position
126+
127+
Returns:
128+
np.array: array containing the evaluated value of constraints
129+
"""
130+
return [np.clip(f(pos), 0, np.inf) for f in self.constraints]
131+
132+
def _phi(self, pos):
133+
"""
134+
Compute the merit function Φ used in COBYLA to evaluate candidate points. Given by:
135+
136+
Φ(x) = f(x) + μ * max(c_i(x))
137+
138+
Args:
139+
pos (np.array): The point in parameter space at which to evaluate the merit function.
140+
141+
Returns:
142+
float: The value of the merit function Φ at the given point.
143+
"""
144+
c = self._eval_constraints(pos)
145+
return self.objective_function(pos) + self.mu * np.max(c)
116146

117-
def _merit_value(self, pos):
118-
# TODO: write the merit function using the _eval_constraints
119-
return 0
147+
def _rearrange_optimum_to_top(self):
148+
"""
149+
Rearrages simplex vertices such that first row is the optimal position
150+
"""
151+
opt_idx = np.argmin([self._phi(vert) for vert in self.simplex])
152+
if opt_idx != 0:
153+
self.simplex[[0, opt_idx]] = self.simplex[[opt_idx, 0]]
120154

121155
def __init__(
122156
self,
@@ -130,7 +164,7 @@ def __init__(
130164
rand_rest_p=0,
131165
nth_process=None,
132166
alpha = 0.25,
133-
beta = 2.1
167+
beta = 2.1,
134168
):
135169
super().__init__(
136170
search_space=search_space,
@@ -145,6 +179,8 @@ def __init__(
145179
self.rho_beg = rho_beg
146180
self.rho_end = rho_end
147181
self._rho = rho_beg
182+
183+
self._mu = 0
148184
self.state = 0
149185
self.FLAG = 0
150186

@@ -167,7 +203,7 @@ def _score(self, pos):
167203

168204
def evaluate(self, pos):
169205
# TODO: Impl
170-
return self._merit_value(pos)
206+
return self._phi(pos)
171207

172208
def finish_search(self):
173209
# TODO: Impl

0 commit comments

Comments
 (0)