Source code for optymus.methods.zero_order._powell
import time
import jax
import jax.numpy as jnp
from tqdm.auto import tqdm
from optymus.methods.utils import BaseOptimizer
from optymus.methods.utils._result import OptimizeResult
class Powell(BaseOptimizer):
r"""Powell's Method
Powell's method is a zero-order optimization method that uses a set of basis vectors to search for the minimum of a function.
We can describe the method with the equation:
.. math::
x_{k+1} = x_k + \alpha_1 v_1 + \alpha_2 v_2 + \cdots + \alpha_n v_n
where $x_k$ is the current point, $\alpha_i$ is the learning rate, and $v_i$ is the basis vector.
Parameters
----------
f_obj : callable
Objective function to minimize
f_cons : callable
Constraint function
args : tuple
Arguments for the objective function
args_cons : tuple
Arguments for the constraint function
x0 : ndarray
Initial guess
tol : float
Tolerance for stopping criteria
learning_rate : float
Lerning rate for line search
max_iter : int
Maximum number of iterations
maximize : bool
If True, maximize the objective function
Returns
-------
dict
method_name : str
Method name
xopt : ndarray
Optimal point
fmin : float
Minimum value
num_iter : int
Number of iterations
path : ndarray
Path taken
alphas : ndarray
Lerning rate for line searchs
"""
def optimize(self):
start_time = time.time()
x = self.x0.astype(float)
grad = jax.grad(self.f_obj)
# basis vectors
def basis(i, n):
return jnp.eye(n)[:, i - 1]
n = len(x)
u = [basis(i, n) for i in range(1, n + 1)] # Initial basis vectors
path = [x]
alphas = []
f_history = [float(self.penalized_obj(x))]
grad_norms = []
num_iter = 0
termination_reason = "max_iter_reached"
progress_bar = tqdm(range(self.max_iter), desc="Powell", disable=not self.verbose)
for _ in progress_bar:
# Perform line search along the basis vectors
g_norm = float(jnp.linalg.norm(grad(x)))
grad_norms.append(g_norm)
if g_norm < self.tol:
termination_reason = "gradient_norm_below_tol"
break
x_prime = x.copy()
for i in range(n):
d = u[i]
r = self._do_line_search(x_prime, d)
x_prime = self.project(r["xopt"])
alphas.append(r["alpha"])
path.append(x_prime)
# Update basis vectors
for i in range(n - 1):
u[i] = u[i + 1]
u[n - 1] = x_prime - x
# Perform line search along the new direction
d = u[n - 1]
r = self._do_line_search(x, d)
x_prime = self.project(r["xopt"])
x = x_prime
alphas.append(r["alpha"])
path.append(x)
f_history.append(float(self.penalized_obj(x)))
num_iter += 1
end_time = time.time()
elapsed_time = end_time - start_time
return OptimizeResult({
"method_name": "Powell" if not self.f_cons else "Powell with Penalty",
"xopt": x,
"fmin": self.f_obj(x, *self.args),
"num_iter": num_iter,
"path": jnp.array(path),
"alphas": jnp.array(alphas),
"f_history": jnp.array(f_history),
"grad_norms": jnp.array(grad_norms),
"termination_reason": termination_reason,
"time": elapsed_time,
})
[docs]
def powell(**kwargs):
optimizer = Powell(**kwargs)
return optimizer.optimize()