_trustregion_ncg.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """Newton-CG trust-region optimization."""
  2. import math
  3. import numpy as np
  4. import scipy.linalg
  5. from ._trustregion import (_minimize_trust_region, BaseQuadraticSubproblem)
  6. __all__ = []
  7. def _minimize_trust_ncg(fun, x0, args=(), jac=None, hess=None, hessp=None,
  8. **trust_region_options):
  9. """
  10. Minimization of scalar function of one or more variables using
  11. the Newton conjugate gradient trust-region algorithm.
  12. Options
  13. -------
  14. initial_trust_radius : float
  15. Initial trust-region radius.
  16. max_trust_radius : float
  17. Maximum value of the trust-region radius. No steps that are longer
  18. than this value will be proposed.
  19. eta : float
  20. Trust region related acceptance stringency for proposed steps.
  21. gtol : float
  22. Gradient norm must be less than `gtol` before successful
  23. termination.
  24. """
  25. if jac is None:
  26. raise ValueError('Jacobian is required for Newton-CG trust-region '
  27. 'minimization')
  28. if hess is None and hessp is None:
  29. raise ValueError('Either the Hessian or the Hessian-vector product '
  30. 'is required for Newton-CG trust-region minimization')
  31. return _minimize_trust_region(fun, x0, args=args, jac=jac, hess=hess,
  32. hessp=hessp, subproblem=CGSteihaugSubproblem,
  33. **trust_region_options)
  34. class CGSteihaugSubproblem(BaseQuadraticSubproblem):
  35. """Quadratic subproblem solved by a conjugate gradient method"""
  36. def solve(self, trust_radius):
  37. """
  38. Solve the subproblem using a conjugate gradient method.
  39. Parameters
  40. ----------
  41. trust_radius : float
  42. We are allowed to wander only this far away from the origin.
  43. Returns
  44. -------
  45. p : ndarray
  46. The proposed step.
  47. hits_boundary : bool
  48. True if the proposed step is on the boundary of the trust region.
  49. Notes
  50. -----
  51. This is algorithm (7.2) of Nocedal and Wright 2nd edition.
  52. Only the function that computes the Hessian-vector product is required.
  53. The Hessian itself is not required, and the Hessian does
  54. not need to be positive semidefinite.
  55. """
  56. # get the norm of jacobian and define the origin
  57. p_origin = np.zeros_like(self.jac)
  58. # define a default tolerance
  59. tolerance = min(0.5, math.sqrt(self.jac_mag)) * self.jac_mag
  60. # Stop the method if the search direction
  61. # is a direction of nonpositive curvature.
  62. if self.jac_mag < tolerance:
  63. hits_boundary = False
  64. return p_origin, hits_boundary
  65. # init the state for the first iteration
  66. z = p_origin
  67. r = self.jac
  68. d = -r
  69. # Search for the min of the approximation of the objective function.
  70. while True:
  71. # do an iteration
  72. Bd = self.hessp(d)
  73. dBd = np.dot(d, Bd)
  74. if dBd <= 0:
  75. # Look at the two boundary points.
  76. # Find both values of t to get the boundary points such that
  77. # ||z + t d|| == trust_radius
  78. # and then choose the one with the predicted min value.
  79. ta, tb = self.get_boundaries_intersections(z, d, trust_radius)
  80. pa = z + ta * d
  81. pb = z + tb * d
  82. if self(pa) < self(pb):
  83. p_boundary = pa
  84. else:
  85. p_boundary = pb
  86. hits_boundary = True
  87. return p_boundary, hits_boundary
  88. r_squared = np.dot(r, r)
  89. alpha = r_squared / dBd
  90. z_next = z + alpha * d
  91. if scipy.linalg.norm(z_next) >= trust_radius:
  92. # Find t >= 0 to get the boundary point such that
  93. # ||z + t d|| == trust_radius
  94. ta, tb = self.get_boundaries_intersections(z, d, trust_radius)
  95. p_boundary = z + tb * d
  96. hits_boundary = True
  97. return p_boundary, hits_boundary
  98. r_next = r + alpha * Bd
  99. r_next_squared = np.dot(r_next, r_next)
  100. if math.sqrt(r_next_squared) < tolerance:
  101. hits_boundary = False
  102. return z_next, hits_boundary
  103. beta_next = r_next_squared / r_squared
  104. d_next = -r_next + beta_next * d
  105. # update the state for the next iteration
  106. z = z_next
  107. r = r_next
  108. d = d_next