Source code for smt_optim.subsolvers.multistart
from dataclasses import dataclass
import numpy as np
import scipy.optimize as so
import scipy.stats as stats
from smt_optim.utils.constraints import compute_rscv_sp
[docs]
@dataclass
class MultistartResult:
x: np.ndarray
fun: float
multi_x0: np.ndarray
multi_x: np.ndarray
multi_f: np.ndarray
multi_rscv: np.ndarray
multi_sp_res: list # ScipPy results
[docs]
def multistart_minimize(func, bounds, **kwargs):
num_dim = bounds.shape[0]
# constraints
constraints = kwargs.pop('constraints', [])
num_cstr = len(constraints)
# multistart number
n_start = kwargs.pop('n_start', 10*num_dim)
multi_x0 = kwargs.pop('multi_x0', None)
# tolerance
tol = kwargs.pop('tol', np.sqrt(np.finfo(float).eps))
# max iteration per start
max_iter = kwargs.pop('max_iter', 50*num_dim)
# seed for reproducibility
seed = kwargs.pop('seed', None)
if kwargs:
raise TypeError(f"Unexpected keyword arguments: {list(kwargs.keys())}")
if multi_x0 is None:
sampler = stats.qmc.LatinHypercube(d=num_dim, seed=seed) # (tested) with random_state = None -> random
multi_x0 = sampler.random(n_start)
multi_x0 = stats.qmc.scale(multi_x0, bounds[:, 0], bounds[:, 1])
else:
n_start = multi_x0.shape[0]
multi_x = np.empty_like(multi_x0)
multi_f = np.empty(multi_x0.shape[0])
multi_rscv = np.zeros(multi_x0.shape[0])
multi_sp_res = []
# unconstrained problem --> use L-BFGS-B
if num_cstr == 0:
method = "L-BFGS-B"
# constrained problem --> use SLSQP
else:
method = "SLSQP"
for i in range(multi_x0.shape[0]):
res = so.minimize(func,
x0=multi_x0[i, :],
bounds=bounds,
method=method,
constraints=constraints,
tol=tol,
options={"maxiter": 50 * num_dim})
# check bounds
x = np.clip(res.x, bounds[:, 0], bounds[:, 1])
multi_x[i, :] = x
multi_f[i] = func(res.x)
multi_sp_res.append(res)
if num_cstr > 0:
multi_rscv[i] = compute_rscv_sp(x, constraints)
if num_cstr == 0:
feas_mask = np.full(n_start, True)
else:
feas_mask = multi_rscv <= tol*2
if len(multi_f[feas_mask]) != 0:
idx = np.argmin(multi_f[feas_mask])
fmin = multi_f[feas_mask][idx]
xmin = multi_x[feas_mask][idx]
else:
idx = np.argmin(multi_rscv)
fmin = multi_f[idx]
xmin = multi_x[idx]
# TODO: add final optimization round
res = MultistartResult(
x = xmin,
fun = fmin,
multi_x0 = multi_x0,
multi_x = multi_x,
multi_f = multi_f,
multi_rscv = multi_rscv,
multi_sp_res = multi_sp_res,
)
return res