|
"""Bounded-variable least-squares algorithm.""" |
|
import numpy as np |
|
from numpy.linalg import norm, lstsq |
|
from scipy.optimize import OptimizeResult |
|
|
|
from .common import print_header_linear, print_iteration_linear |
|
|
|
|
|
def compute_kkt_optimality(g, on_bound): |
|
"""Compute the maximum violation of KKT conditions.""" |
|
g_kkt = g * on_bound |
|
free_set = on_bound == 0 |
|
g_kkt[free_set] = np.abs(g[free_set]) |
|
return np.max(g_kkt) |
|
|
|
|
|
def bvls(A, b, x_lsq, lb, ub, tol, max_iter, verbose, rcond=None): |
|
m, n = A.shape |
|
|
|
x = x_lsq.copy() |
|
on_bound = np.zeros(n) |
|
|
|
mask = x <= lb |
|
x[mask] = lb[mask] |
|
on_bound[mask] = -1 |
|
|
|
mask = x >= ub |
|
x[mask] = ub[mask] |
|
on_bound[mask] = 1 |
|
|
|
free_set = on_bound == 0 |
|
active_set = ~free_set |
|
free_set, = np.nonzero(free_set) |
|
|
|
r = A.dot(x) - b |
|
cost = 0.5 * np.dot(r, r) |
|
initial_cost = cost |
|
g = A.T.dot(r) |
|
|
|
cost_change = None |
|
step_norm = None |
|
iteration = 0 |
|
|
|
if verbose == 2: |
|
print_header_linear() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while free_set.size > 0: |
|
if verbose == 2: |
|
optimality = compute_kkt_optimality(g, on_bound) |
|
print_iteration_linear(iteration, cost, cost_change, step_norm, |
|
optimality) |
|
|
|
iteration += 1 |
|
x_free_old = x[free_set].copy() |
|
|
|
A_free = A[:, free_set] |
|
b_free = b - A.dot(x * active_set) |
|
z = lstsq(A_free, b_free, rcond=rcond)[0] |
|
|
|
lbv = z < lb[free_set] |
|
ubv = z > ub[free_set] |
|
v = lbv | ubv |
|
|
|
if np.any(lbv): |
|
ind = free_set[lbv] |
|
x[ind] = lb[ind] |
|
active_set[ind] = True |
|
on_bound[ind] = -1 |
|
|
|
if np.any(ubv): |
|
ind = free_set[ubv] |
|
x[ind] = ub[ind] |
|
active_set[ind] = True |
|
on_bound[ind] = 1 |
|
|
|
ind = free_set[~v] |
|
x[ind] = z[~v] |
|
|
|
r = A.dot(x) - b |
|
cost_new = 0.5 * np.dot(r, r) |
|
cost_change = cost - cost_new |
|
cost = cost_new |
|
g = A.T.dot(r) |
|
step_norm = norm(x[free_set] - x_free_old) |
|
|
|
if np.any(v): |
|
free_set = free_set[~v] |
|
else: |
|
break |
|
|
|
if max_iter is None: |
|
max_iter = n |
|
max_iter += iteration |
|
|
|
termination_status = None |
|
|
|
|
|
|
|
optimality = compute_kkt_optimality(g, on_bound) |
|
for iteration in range(iteration, max_iter): |
|
if verbose == 2: |
|
print_iteration_linear(iteration, cost, cost_change, |
|
step_norm, optimality) |
|
|
|
if optimality < tol: |
|
termination_status = 1 |
|
|
|
if termination_status is not None: |
|
break |
|
|
|
move_to_free = np.argmax(g * on_bound) |
|
on_bound[move_to_free] = 0 |
|
|
|
while True: |
|
|
|
free_set = on_bound == 0 |
|
active_set = ~free_set |
|
free_set, = np.nonzero(free_set) |
|
|
|
x_free = x[free_set] |
|
x_free_old = x_free.copy() |
|
lb_free = lb[free_set] |
|
ub_free = ub[free_set] |
|
|
|
A_free = A[:, free_set] |
|
b_free = b - A.dot(x * active_set) |
|
z = lstsq(A_free, b_free, rcond=rcond)[0] |
|
|
|
lbv, = np.nonzero(z < lb_free) |
|
ubv, = np.nonzero(z > ub_free) |
|
v = np.hstack((lbv, ubv)) |
|
|
|
if v.size > 0: |
|
alphas = np.hstack(( |
|
lb_free[lbv] - x_free[lbv], |
|
ub_free[ubv] - x_free[ubv])) / (z[v] - x_free[v]) |
|
|
|
i = np.argmin(alphas) |
|
i_free = v[i] |
|
alpha = alphas[i] |
|
|
|
x_free *= 1 - alpha |
|
x_free += alpha * z |
|
x[free_set] = x_free |
|
|
|
if i < lbv.size: |
|
on_bound[free_set[i_free]] = -1 |
|
else: |
|
on_bound[free_set[i_free]] = 1 |
|
else: |
|
x_free = z |
|
x[free_set] = x_free |
|
break |
|
|
|
step_norm = norm(x_free - x_free_old) |
|
|
|
r = A.dot(x) - b |
|
cost_new = 0.5 * np.dot(r, r) |
|
cost_change = cost - cost_new |
|
|
|
if cost_change < tol * cost: |
|
termination_status = 2 |
|
cost = cost_new |
|
|
|
g = A.T.dot(r) |
|
optimality = compute_kkt_optimality(g, on_bound) |
|
|
|
if termination_status is None: |
|
termination_status = 0 |
|
|
|
return OptimizeResult( |
|
x=x, fun=r, cost=cost, optimality=optimality, active_mask=on_bound, |
|
nit=iteration + 1, status=termination_status, |
|
initial_cost=initial_cost) |
|
|