bvls.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Bounded-variable least-squares algorithm."""
  2. import numpy as np
  3. from numpy.linalg import norm, lstsq
  4. from scipy.optimize import OptimizeResult
  5. from .common import print_header_linear, print_iteration_linear
  6. def compute_kkt_optimality(g, on_bound):
  7. """Compute the maximum violation of KKT conditions."""
  8. g_kkt = g * on_bound
  9. free_set = on_bound == 0
  10. g_kkt[free_set] = np.abs(g[free_set])
  11. return np.max(g_kkt)
  12. def bvls(A, b, x_lsq, lb, ub, tol, max_iter, verbose, rcond=None):
  13. m, n = A.shape
  14. x = x_lsq.copy()
  15. on_bound = np.zeros(n)
  16. mask = x <= lb
  17. x[mask] = lb[mask]
  18. on_bound[mask] = -1
  19. mask = x >= ub
  20. x[mask] = ub[mask]
  21. on_bound[mask] = 1
  22. free_set = on_bound == 0
  23. active_set = ~free_set
  24. free_set, = np.nonzero(free_set)
  25. r = A.dot(x) - b
  26. cost = 0.5 * np.dot(r, r)
  27. initial_cost = cost
  28. g = A.T.dot(r)
  29. cost_change = None
  30. step_norm = None
  31. iteration = 0
  32. if verbose == 2:
  33. print_header_linear()
  34. # This is the initialization loop. The requirement is that the
  35. # least-squares solution on free variables is feasible before BVLS starts.
  36. # One possible initialization is to set all variables to lower or upper
  37. # bounds, but many iterations may be required from this state later on.
  38. # The implemented ad-hoc procedure which intuitively should give a better
  39. # initial state: find the least-squares solution on current free variables,
  40. # if its feasible then stop, otherwise, set violating variables to
  41. # corresponding bounds and continue on the reduced set of free variables.
  42. while free_set.size > 0:
  43. if verbose == 2:
  44. optimality = compute_kkt_optimality(g, on_bound)
  45. print_iteration_linear(iteration, cost, cost_change, step_norm,
  46. optimality)
  47. iteration += 1
  48. x_free_old = x[free_set].copy()
  49. A_free = A[:, free_set]
  50. b_free = b - A.dot(x * active_set)
  51. z = lstsq(A_free, b_free, rcond=rcond)[0]
  52. lbv = z < lb[free_set]
  53. ubv = z > ub[free_set]
  54. v = lbv | ubv
  55. if np.any(lbv):
  56. ind = free_set[lbv]
  57. x[ind] = lb[ind]
  58. active_set[ind] = True
  59. on_bound[ind] = -1
  60. if np.any(ubv):
  61. ind = free_set[ubv]
  62. x[ind] = ub[ind]
  63. active_set[ind] = True
  64. on_bound[ind] = 1
  65. ind = free_set[~v]
  66. x[ind] = z[~v]
  67. r = A.dot(x) - b
  68. cost_new = 0.5 * np.dot(r, r)
  69. cost_change = cost - cost_new
  70. cost = cost_new
  71. g = A.T.dot(r)
  72. step_norm = norm(x[free_set] - x_free_old)
  73. if np.any(v):
  74. free_set = free_set[~v]
  75. else:
  76. break
  77. if max_iter is None:
  78. max_iter = n
  79. max_iter += iteration
  80. termination_status = None
  81. # Main BVLS loop.
  82. optimality = compute_kkt_optimality(g, on_bound)
  83. for iteration in range(iteration, max_iter): # BVLS Loop A
  84. if verbose == 2:
  85. print_iteration_linear(iteration, cost, cost_change,
  86. step_norm, optimality)
  87. if optimality < tol:
  88. termination_status = 1
  89. if termination_status is not None:
  90. break
  91. move_to_free = np.argmax(g * on_bound)
  92. on_bound[move_to_free] = 0
  93. while True: # BVLS Loop B
  94. free_set = on_bound == 0
  95. active_set = ~free_set
  96. free_set, = np.nonzero(free_set)
  97. x_free = x[free_set]
  98. x_free_old = x_free.copy()
  99. lb_free = lb[free_set]
  100. ub_free = ub[free_set]
  101. A_free = A[:, free_set]
  102. b_free = b - A.dot(x * active_set)
  103. z = lstsq(A_free, b_free, rcond=rcond)[0]
  104. lbv, = np.nonzero(z < lb_free)
  105. ubv, = np.nonzero(z > ub_free)
  106. v = np.hstack((lbv, ubv))
  107. if v.size > 0:
  108. alphas = np.hstack((
  109. lb_free[lbv] - x_free[lbv],
  110. ub_free[ubv] - x_free[ubv])) / (z[v] - x_free[v])
  111. i = np.argmin(alphas)
  112. i_free = v[i]
  113. alpha = alphas[i]
  114. x_free *= 1 - alpha
  115. x_free += alpha * z
  116. x[free_set] = x_free
  117. if i < lbv.size:
  118. on_bound[free_set[i_free]] = -1
  119. else:
  120. on_bound[free_set[i_free]] = 1
  121. else:
  122. x_free = z
  123. x[free_set] = x_free
  124. break
  125. step_norm = norm(x_free - x_free_old)
  126. r = A.dot(x) - b
  127. cost_new = 0.5 * np.dot(r, r)
  128. cost_change = cost - cost_new
  129. if cost_change < tol * cost:
  130. termination_status = 2
  131. cost = cost_new
  132. g = A.T.dot(r)
  133. optimality = compute_kkt_optimality(g, on_bound)
  134. if termination_status is None:
  135. termination_status = 0
  136. return OptimizeResult(
  137. x=x, fun=r, cost=cost, optimality=optimality, active_mask=on_bound,
  138. nit=iteration + 1, status=termination_status,
  139. initial_cost=initial_cost)