qp_subproblem.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. """Equality-constrained quadratic programming solvers."""
  2. from __future__ import division, print_function, absolute_import
  3. from scipy.sparse import (linalg, bmat, csc_matrix)
  4. from math import copysign
  5. import numpy as np
  6. from numpy.linalg import norm
  7. __all__ = [
  8. 'eqp_kktfact',
  9. 'sphere_intersections',
  10. 'box_intersections',
  11. 'box_sphere_intersections',
  12. 'inside_box_boundaries',
  13. 'modified_dogleg',
  14. 'projected_cg'
  15. ]
  16. # For comparison with the projected CG
  17. def eqp_kktfact(H, c, A, b):
  18. """Solve equality-constrained quadratic programming (EQP) problem.
  19. Solve ``min 1/2 x.T H x + x.t c`` subject to ``A x + b = 0``
  20. using direct factorization of the KKT system.
  21. Parameters
  22. ----------
  23. H : sparse matrix, shape (n, n)
  24. Hessian matrix of the EQP problem.
  25. c : array_like, shape (n,)
  26. Gradient of the quadratic objective function.
  27. A : sparse matrix
  28. Jacobian matrix of the EQP problem.
  29. b : array_like, shape (m,)
  30. Right-hand side of the constraint equation.
  31. Returns
  32. -------
  33. x : array_like, shape (n,)
  34. Solution of the KKT problem.
  35. lagrange_multipliers : ndarray, shape (m,)
  36. Lagrange multipliers of the KKT problem.
  37. """
  38. n, = np.shape(c) # Number of parameters
  39. m, = np.shape(b) # Number of constraints
  40. # Karush-Kuhn-Tucker matrix of coefficients.
  41. # Defined as in Nocedal/Wright "Numerical
  42. # Optimization" p.452 in Eq. (16.4).
  43. kkt_matrix = csc_matrix(bmat([[H, A.T], [A, None]]))
  44. # Vector of coefficients.
  45. kkt_vec = np.hstack([-c, -b])
  46. # TODO: Use a symmetric indefinite factorization
  47. # to solve the system twice as fast (because
  48. # of the symmetry).
  49. lu = linalg.splu(kkt_matrix)
  50. kkt_sol = lu.solve(kkt_vec)
  51. x = kkt_sol[:n]
  52. lagrange_multipliers = -kkt_sol[n:n+m]
  53. return x, lagrange_multipliers
  54. def sphere_intersections(z, d, trust_radius,
  55. entire_line=False):
  56. """Find the intersection between segment (or line) and spherical constraints.
  57. Find the intersection between the segment (or line) defined by the
  58. parametric equation ``x(t) = z + t*d`` and the ball
  59. ``||x|| <= trust_radius``.
  60. Parameters
  61. ----------
  62. z : array_like, shape (n,)
  63. Initial point.
  64. d : array_like, shape (n,)
  65. Direction.
  66. trust_radius : float
  67. Ball radius.
  68. entire_line : bool, optional
  69. When ``True`` the function returns the intersection between the line
  70. ``x(t) = z + t*d`` (``t`` can assume any value) and the ball
  71. ``||x|| <= trust_radius``. When ``False`` returns the intersection
  72. between the segment ``x(t) = z + t*d``, ``0 <= t <= 1``, and the ball.
  73. Returns
  74. -------
  75. ta, tb : float
  76. The line/segment ``x(t) = z + t*d`` is inside the ball for
  77. for ``ta <= t <= tb``.
  78. intersect : bool
  79. When ``True`` there is a intersection between the line/segment
  80. and the sphere. On the other hand, when ``False``, there is no
  81. intersection.
  82. """
  83. # Special case when d=0
  84. if norm(d) == 0:
  85. return 0, 0, False
  86. # Check for inf trust_radius
  87. if np.isinf(trust_radius):
  88. if entire_line:
  89. ta = -np.inf
  90. tb = np.inf
  91. else:
  92. ta = 0
  93. tb = 1
  94. intersect = True
  95. return ta, tb, intersect
  96. a = np.dot(d, d)
  97. b = 2 * np.dot(z, d)
  98. c = np.dot(z, z) - trust_radius**2
  99. discriminant = b*b - 4*a*c
  100. if discriminant < 0:
  101. intersect = False
  102. return 0, 0, intersect
  103. sqrt_discriminant = np.sqrt(discriminant)
  104. # The following calculation is mathematically
  105. # equivalent to:
  106. # ta = (-b - sqrt_discriminant) / (2*a)
  107. # tb = (-b + sqrt_discriminant) / (2*a)
  108. # but produce smaller round off errors.
  109. # Look at Matrix Computation p.97
  110. # for a better justification.
  111. aux = b + copysign(sqrt_discriminant, b)
  112. ta = -aux / (2*a)
  113. tb = -2*c / aux
  114. ta, tb = sorted([ta, tb])
  115. if entire_line:
  116. intersect = True
  117. else:
  118. # Checks to see if intersection happens
  119. # within vectors length.
  120. if tb < 0 or ta > 1:
  121. intersect = False
  122. ta = 0
  123. tb = 0
  124. else:
  125. intersect = True
  126. # Restrict intersection interval
  127. # between 0 and 1.
  128. ta = max(0, ta)
  129. tb = min(1, tb)
  130. return ta, tb, intersect
  131. def box_intersections(z, d, lb, ub,
  132. entire_line=False):
  133. """Find the intersection between segment (or line) and box constraints.
  134. Find the intersection between the segment (or line) defined by the
  135. parametric equation ``x(t) = z + t*d`` and the rectangular box
  136. ``lb <= x <= ub``.
  137. Parameters
  138. ----------
  139. z : array_like, shape (n,)
  140. Initial point.
  141. d : array_like, shape (n,)
  142. Direction.
  143. lb : array_like, shape (n,)
  144. Lower bounds to each one of the components of ``x``. Used
  145. to delimit the rectangular box.
  146. ub : array_like, shape (n, )
  147. Upper bounds to each one of the components of ``x``. Used
  148. to delimit the rectangular box.
  149. entire_line : bool, optional
  150. When ``True`` the function returns the intersection between the line
  151. ``x(t) = z + t*d`` (``t`` can assume any value) and the rectangular
  152. box. When ``False`` returns the intersection between the segment
  153. ``x(t) = z + t*d``, ``0 <= t <= 1``, and the rectangular box.
  154. Returns
  155. -------
  156. ta, tb : float
  157. The line/segment ``x(t) = z + t*d`` is inside the box for
  158. for ``ta <= t <= tb``.
  159. intersect : bool
  160. When ``True`` there is a intersection between the line (or segment)
  161. and the rectangular box. On the other hand, when ``False``, there is no
  162. intersection.
  163. """
  164. # Make sure it is a numpy array
  165. z = np.asarray(z)
  166. d = np.asarray(d)
  167. lb = np.asarray(lb)
  168. ub = np.asarray(ub)
  169. # Special case when d=0
  170. if norm(d) == 0:
  171. return 0, 0, False
  172. # Get values for which d==0
  173. zero_d = (d == 0)
  174. # If the boundaries are not satisfied for some coordinate
  175. # for which "d" is zero, there is no box-line intersection.
  176. if (z[zero_d] < lb[zero_d]).any() or (z[zero_d] > ub[zero_d]).any():
  177. intersect = False
  178. return 0, 0, intersect
  179. # Remove values for which d is zero
  180. not_zero_d = np.logical_not(zero_d)
  181. z = z[not_zero_d]
  182. d = d[not_zero_d]
  183. lb = lb[not_zero_d]
  184. ub = ub[not_zero_d]
  185. # Find a series of intervals (t_lb[i], t_ub[i]).
  186. t_lb = (lb-z) / d
  187. t_ub = (ub-z) / d
  188. # Get the intersection of all those intervals.
  189. ta = max(np.minimum(t_lb, t_ub))
  190. tb = min(np.maximum(t_lb, t_ub))
  191. # Check if intersection is feasible
  192. if ta <= tb:
  193. intersect = True
  194. else:
  195. intersect = False
  196. # Checks to see if intersection happens within vectors length.
  197. if not entire_line:
  198. if tb < 0 or ta > 1:
  199. intersect = False
  200. ta = 0
  201. tb = 0
  202. else:
  203. # Restrict intersection interval between 0 and 1.
  204. ta = max(0, ta)
  205. tb = min(1, tb)
  206. return ta, tb, intersect
  207. def box_sphere_intersections(z, d, lb, ub, trust_radius,
  208. entire_line=False,
  209. extra_info=False):
  210. """Find the intersection between segment (or line) and box/sphere constraints.
  211. Find the intersection between the segment (or line) defined by the
  212. parametric equation ``x(t) = z + t*d``, the rectangular box
  213. ``lb <= x <= ub`` and the ball ``||x|| <= trust_radius``.
  214. Parameters
  215. ----------
  216. z : array_like, shape (n,)
  217. Initial point.
  218. d : array_like, shape (n,)
  219. Direction.
  220. lb : array_like, shape (n,)
  221. Lower bounds to each one of the components of ``x``. Used
  222. to delimit the rectangular box.
  223. ub : array_like, shape (n, )
  224. Upper bounds to each one of the components of ``x``. Used
  225. to delimit the rectangular box.
  226. trust_radius : float
  227. Ball radius.
  228. entire_line : bool, optional
  229. When ``True`` the function returns the intersection between the line
  230. ``x(t) = z + t*d`` (``t`` can assume any value) and the constraints.
  231. When ``False`` returns the intersection between the segment
  232. ``x(t) = z + t*d``, ``0 <= t <= 1`` and the constraints.
  233. extra_info : bool, optional
  234. When ``True`` returns ``intersect_sphere`` and ``intersect_box``.
  235. Returns
  236. -------
  237. ta, tb : float
  238. The line/segment ``x(t) = z + t*d`` is inside the rectangular box and
  239. inside the ball for for ``ta <= t <= tb``.
  240. intersect : bool
  241. When ``True`` there is a intersection between the line (or segment)
  242. and both constraints. On the other hand, when ``False``, there is no
  243. intersection.
  244. sphere_info : dict, optional
  245. Dictionary ``{ta, tb, intersect}`` containing the interval ``[ta, tb]``
  246. for which the line intercept the ball. And a boolean value indicating
  247. whether the sphere is intersected by the line.
  248. box_info : dict, optional
  249. Dictionary ``{ta, tb, intersect}`` containing the interval ``[ta, tb]``
  250. for which the line intercept the box. And a boolean value indicating
  251. whether the box is intersected by the line.
  252. """
  253. ta_b, tb_b, intersect_b = box_intersections(z, d, lb, ub,
  254. entire_line)
  255. ta_s, tb_s, intersect_s = sphere_intersections(z, d,
  256. trust_radius,
  257. entire_line)
  258. ta = np.maximum(ta_b, ta_s)
  259. tb = np.minimum(tb_b, tb_s)
  260. if intersect_b and intersect_s and ta <= tb:
  261. intersect = True
  262. else:
  263. intersect = False
  264. if extra_info:
  265. sphere_info = {'ta': ta_s, 'tb': tb_s, 'intersect': intersect_s}
  266. box_info = {'ta': ta_b, 'tb': tb_b, 'intersect': intersect_b}
  267. return ta, tb, intersect, sphere_info, box_info
  268. else:
  269. return ta, tb, intersect
  270. def inside_box_boundaries(x, lb, ub):
  271. """Check if lb <= x <= ub."""
  272. return (lb <= x).all() and (x <= ub).all()
  273. def reinforce_box_boundaries(x, lb, ub):
  274. """Return clipped value of x"""
  275. return np.minimum(np.maximum(x, lb), ub)
  276. def modified_dogleg(A, Y, b, trust_radius, lb, ub):
  277. """Approximately minimize ``1/2*|| A x + b ||^2`` inside trust-region.
  278. Approximately solve the problem of minimizing ``1/2*|| A x + b ||^2``
  279. subject to ``||x|| < Delta`` and ``lb <= x <= ub`` using a modification
  280. of the classical dogleg approach.
  281. Parameters
  282. ----------
  283. A : LinearOperator (or sparse matrix or ndarray), shape (m, n)
  284. Matrix ``A`` in the minimization problem. It should have
  285. dimension ``(m, n)`` such that ``m < n``.
  286. Y : LinearOperator (or sparse matrix or ndarray), shape (n, m)
  287. LinearOperator that apply the projection matrix
  288. ``Q = A.T inv(A A.T)`` to the vector. The obtained vector
  289. ``y = Q x`` being the minimum norm solution of ``A y = x``.
  290. b : array_like, shape (m,)
  291. Vector ``b``in the minimization problem.
  292. trust_radius: float
  293. Trust radius to be considered. Delimits a sphere boundary
  294. to the problem.
  295. lb : array_like, shape (n,)
  296. Lower bounds to each one of the components of ``x``.
  297. It is expected that ``lb <= 0``, otherwise the algorithm
  298. may fail. If ``lb[i] = -Inf`` the lower
  299. bound for the i-th component is just ignored.
  300. ub : array_like, shape (n, )
  301. Upper bounds to each one of the components of ``x``.
  302. It is expected that ``ub >= 0``, otherwise the algorithm
  303. may fail. If ``ub[i] = Inf`` the upper bound for the i-th
  304. component is just ignored.
  305. Returns
  306. -------
  307. x : array_like, shape (n,)
  308. Solution to the problem.
  309. Notes
  310. -----
  311. Based on implementations described in p.p. 885-886 from [1]_.
  312. References
  313. ----------
  314. .. [1] Byrd, Richard H., Mary E. Hribar, and Jorge Nocedal.
  315. "An interior point algorithm for large-scale nonlinear
  316. programming." SIAM Journal on Optimization 9.4 (1999): 877-900.
  317. """
  318. # Compute minimum norm minimizer of 1/2*|| A x + b ||^2.
  319. newton_point = -Y.dot(b)
  320. # Check for interior point
  321. if inside_box_boundaries(newton_point, lb, ub) \
  322. and norm(newton_point) <= trust_radius:
  323. x = newton_point
  324. return x
  325. # Compute gradient vector ``g = A.T b``
  326. g = A.T.dot(b)
  327. # Compute cauchy point
  328. # `cauchy_point = g.T g / (g.T A.T A g)``.
  329. A_g = A.dot(g)
  330. cauchy_point = -np.dot(g, g) / np.dot(A_g, A_g) * g
  331. # Origin
  332. origin_point = np.zeros_like(cauchy_point)
  333. # Check the segment between cauchy_point and newton_point
  334. # for a possible solution.
  335. z = cauchy_point
  336. p = newton_point - cauchy_point
  337. _, alpha, intersect = box_sphere_intersections(z, p, lb, ub,
  338. trust_radius)
  339. if intersect:
  340. x1 = z + alpha*p
  341. else:
  342. # Check the segment between the origin and cauchy_point
  343. # for a possible solution.
  344. z = origin_point
  345. p = cauchy_point
  346. _, alpha, _ = box_sphere_intersections(z, p, lb, ub,
  347. trust_radius)
  348. x1 = z + alpha*p
  349. # Check the segment between origin and newton_point
  350. # for a possible solution.
  351. z = origin_point
  352. p = newton_point
  353. _, alpha, _ = box_sphere_intersections(z, p, lb, ub,
  354. trust_radius)
  355. x2 = z + alpha*p
  356. # Return the best solution among x1 and x2.
  357. if norm(A.dot(x1) + b) < norm(A.dot(x2) + b):
  358. return x1
  359. else:
  360. return x2
  361. def projected_cg(H, c, Z, Y, b, trust_radius=np.inf,
  362. lb=None, ub=None, tol=None,
  363. max_iter=None, max_infeasible_iter=None,
  364. return_all=False):
  365. """Solve EQP problem with projected CG method.
  366. Solve equality-constrained quadratic programming problem
  367. ``min 1/2 x.T H x + x.t c`` subject to ``A x + b = 0`` and,
  368. possibly, to trust region constraints ``||x|| < trust_radius``
  369. and box constraints ``lb <= x <= ub``.
  370. Parameters
  371. ----------
  372. H : LinearOperator (or sparse matrix or ndarray), shape (n, n)
  373. Operator for computing ``H v``.
  374. c : array_like, shape (n,)
  375. Gradient of the quadratic objective function.
  376. Z : LinearOperator (or sparse matrix or ndarray), shape (n, n)
  377. Operator for projecting ``x`` into the null space of A.
  378. Y : LinearOperator, sparse matrix, ndarray, shape (n, m)
  379. Operator that, for a given a vector ``b``, compute smallest
  380. norm solution of ``A x + b = 0``.
  381. b : array_like, shape (m,)
  382. Right-hand side of the constraint equation.
  383. trust_radius : float, optional
  384. Trust radius to be considered. By default uses ``trust_radius=inf``,
  385. which means no trust radius at all.
  386. lb : array_like, shape (n,), optional
  387. Lower bounds to each one of the components of ``x``.
  388. If ``lb[i] = -Inf`` the lower bound for the i-th
  389. component is just ignored (default).
  390. ub : array_like, shape (n, ), optional
  391. Upper bounds to each one of the components of ``x``.
  392. If ``ub[i] = Inf`` the upper bound for the i-th
  393. component is just ignored (default).
  394. tol : float, optional
  395. Tolerance used to interrupt the algorithm.
  396. max_iter : int, optional
  397. Maximum algorithm iterations. Where ``max_inter <= n-m``.
  398. By default uses ``max_iter = n-m``.
  399. max_infeasible_iter : int, optional
  400. Maximum infeasible (regarding box constraints) iterations the
  401. algorithm is allowed to take.
  402. By default uses ``max_infeasible_iter = n-m``.
  403. return_all : bool, optional
  404. When ``true`` return the list of all vectors through the iterations.
  405. Returns
  406. -------
  407. x : array_like, shape (n,)
  408. Solution of the EQP problem.
  409. info : Dict
  410. Dictionary containing the following:
  411. - niter : Number of iterations.
  412. - stop_cond : Reason for algorithm termination:
  413. 1. Iteration limit was reached;
  414. 2. Reached the trust-region boundary;
  415. 3. Negative curvature detected;
  416. 4. Tolerance was satisfied.
  417. - allvecs : List containing all intermediary vectors (optional).
  418. - hits_boundary : True if the proposed step is on the boundary
  419. of the trust region.
  420. Notes
  421. -----
  422. Implementation of Algorithm 6.2 on [1]_.
  423. In the absence of spherical and box constraints, for sufficient
  424. iterations, the method returns a truly optimal result.
  425. In the presence of those constraints the value returned is only
  426. a inexpensive approximation of the optimal value.
  427. References
  428. ----------
  429. .. [1] Gould, Nicholas IM, Mary E. Hribar, and Jorge Nocedal.
  430. "On the solution of equality constrained quadratic
  431. programming problems arising in optimization."
  432. SIAM Journal on Scientific Computing 23.4 (2001): 1376-1395.
  433. """
  434. CLOSE_TO_ZERO = 1e-25
  435. n, = np.shape(c) # Number of parameters
  436. m, = np.shape(b) # Number of constraints
  437. # Initial Values
  438. x = Y.dot(-b)
  439. r = Z.dot(H.dot(x) + c)
  440. g = Z.dot(r)
  441. p = -g
  442. # Store ``x`` value
  443. if return_all:
  444. allvecs = [x]
  445. # Values for the first iteration
  446. H_p = H.dot(p)
  447. rt_g = norm(g)**2 # g.T g = r.T Z g = r.T g (ref [1]_ p.1389)
  448. # If x > trust-region the problem does not have a solution.
  449. tr_distance = trust_radius - norm(x)
  450. if tr_distance < 0:
  451. raise ValueError("Trust region problem does not have a solution.")
  452. # If x == trust_radius, then x is the solution
  453. # to the optimization problem, since x is the
  454. # minimum norm solution to Ax=b.
  455. elif tr_distance < CLOSE_TO_ZERO:
  456. info = {'niter': 0, 'stop_cond': 2, 'hits_boundary': True}
  457. if return_all:
  458. allvecs.append(x)
  459. info['allvecs'] = allvecs
  460. return x, info
  461. # Set default tolerance
  462. if tol is None:
  463. tol = max(min(0.01 * np.sqrt(rt_g), 0.1 * rt_g), CLOSE_TO_ZERO)
  464. # Set default lower and upper bounds
  465. if lb is None:
  466. lb = np.full(n, -np.inf)
  467. if ub is None:
  468. ub = np.full(n, np.inf)
  469. # Set maximum iterations
  470. if max_iter is None:
  471. max_iter = n-m
  472. max_iter = min(max_iter, n-m)
  473. # Set maximum infeasible iterations
  474. if max_infeasible_iter is None:
  475. max_infeasible_iter = n-m
  476. hits_boundary = False
  477. stop_cond = 1
  478. counter = 0
  479. last_feasible_x = np.zeros_like(x)
  480. k = 0
  481. for i in range(max_iter):
  482. # Stop criteria - Tolerance : r.T g < tol
  483. if rt_g < tol:
  484. stop_cond = 4
  485. break
  486. k += 1
  487. # Compute curvature
  488. pt_H_p = H_p.dot(p)
  489. # Stop criteria - Negative curvature
  490. if pt_H_p <= 0:
  491. if np.isinf(trust_radius):
  492. raise ValueError("Negative curvature not "
  493. "allowed for unrestrited "
  494. "problems.")
  495. else:
  496. # Find intersection with constraints
  497. _, alpha, intersect = box_sphere_intersections(
  498. x, p, lb, ub, trust_radius, entire_line=True)
  499. # Update solution
  500. if intersect:
  501. x = x + alpha*p
  502. # Reinforce variables are inside box constraints.
  503. # This is only necessary because of roundoff errors.
  504. x = reinforce_box_boundaries(x, lb, ub)
  505. # Attribute information
  506. stop_cond = 3
  507. hits_boundary = True
  508. break
  509. # Get next step
  510. alpha = rt_g / pt_H_p
  511. x_next = x + alpha*p
  512. # Stop criteria - Hits boundary
  513. if np.linalg.norm(x_next) >= trust_radius:
  514. # Find intersection with box constraints
  515. _, theta, intersect = box_sphere_intersections(x, alpha*p, lb, ub,
  516. trust_radius)
  517. # Update solution
  518. if intersect:
  519. x = x + theta*alpha*p
  520. # Reinforce variables are inside box constraints.
  521. # This is only necessary because of roundoff errors.
  522. x = reinforce_box_boundaries(x, lb, ub)
  523. # Attribute information
  524. stop_cond = 2
  525. hits_boundary = True
  526. break
  527. # Check if ``x`` is inside the box and start counter if it is not.
  528. if inside_box_boundaries(x_next, lb, ub):
  529. counter = 0
  530. else:
  531. counter += 1
  532. # Whenever outside box constraints keep looking for intersections.
  533. if counter > 0:
  534. _, theta, intersect = box_sphere_intersections(x, alpha*p, lb, ub,
  535. trust_radius)
  536. if intersect:
  537. last_feasible_x = x + theta*alpha*p
  538. # Reinforce variables are inside box constraints.
  539. # This is only necessary because of roundoff errors.
  540. last_feasible_x = reinforce_box_boundaries(last_feasible_x,
  541. lb, ub)
  542. counter = 0
  543. # Stop after too many infeasible (regarding box constraints) iteration.
  544. if counter > max_infeasible_iter:
  545. break
  546. # Store ``x_next`` value
  547. if return_all:
  548. allvecs.append(x_next)
  549. # Update residual
  550. r_next = r + alpha*H_p
  551. # Project residual g+ = Z r+
  552. g_next = Z.dot(r_next)
  553. # Compute conjugate direction step d
  554. rt_g_next = norm(g_next)**2 # g.T g = r.T g (ref [1]_ p.1389)
  555. beta = rt_g_next / rt_g
  556. p = - g_next + beta*p
  557. # Prepare for next iteration
  558. x = x_next
  559. g = g_next
  560. r = g_next
  561. rt_g = norm(g)**2 # g.T g = r.T Z g = r.T g (ref [1]_ p.1389)
  562. H_p = H.dot(p)
  563. if not inside_box_boundaries(x, lb, ub):
  564. x = last_feasible_x
  565. hits_boundary = True
  566. info = {'niter': k, 'stop_cond': stop_cond,
  567. 'hits_boundary': hits_boundary}
  568. if return_all:
  569. info['allvecs'] = allvecs
  570. return x, info