88
99import numpy as np
1010import scipy .linalg as linalg
11+ import scipy .sparse .linalg as slinalg
1112
1213from numpy .linalg import norm
1314from scipy .sparse import csc_matrix
1415from scipy .optimize import Bounds , NonlinearConstraint , minimize
1516
1617from logging import Logger
1718from .subproblem import (
18- solve_1d_trust_region_subproblem , solve_nd_trust_region_subproblem
19+ solve_1d_trust_region_subproblem , solve_nd_trust_region_subproblem ,
20+ get_1d_trust_region_boundary_solution , quadratic_form
1921)
2022
2123from typing import Union
2224
2325
24- def quadratic_form (Q : np .ndarray , p : np .ndarray , x : np .ndarray ) -> float :
25- """
26- Computes the quadratic form :math:`x^TQx + x^Tp`
27-
28- :param Q: Matrix
29- :param p: Vector
30- :param x: Input
31-
32- :return:
33- Value of form
34- """
35- return 0.5 * x .T .dot (Q ).dot (x ) + p .T .dot (x )
36-
37-
3826def normalize (v : np .ndarray ) -> None :
3927 """
4028 Inplace normalization of a vector
@@ -57,7 +45,7 @@ class Step:
5745 transformed step ss: `ss = subspace * sc`
5846 :ivar ss: Affine transformed step: `s = scaling * ss`
5947 :ivar og_s: `s` without step back
60- :ivar og_sc: `st ` without step back
48+ :ivar og_sc: `sc ` without step back
6149 :ivar og_ss: `ss` without step back
6250 :ivar sg: Rescaled gradient `scaling * g`
6351 :ivar hess: Hessian of the objective function at `x`
@@ -238,7 +226,7 @@ class TRStepFull(Step):
238226 the trust region subproblem.
239227 """
240228
241- type = 'trnd '
229+ type = 'nd '
242230
243231 def __init__ (self , x , sg , hess , scaling , g_dscaling , delta , theta ,
244232 ub , lb , logger ):
@@ -253,7 +241,7 @@ class TRStep2D(Step):
253241 the trust region subproblem according to a 2D subproblem
254242 """
255243
256- type = 'tr2d '
244+ type = '2d '
257245
258246 def __init__ (self , x , sg , hess , scaling , g_dscaling , delta , theta ,
259247 ub , lb , logger ):
@@ -270,8 +258,11 @@ def __init__(self, x, sg, hess, scaling, g_dscaling, delta, theta,
270258 # in this case we are in Case 2 of Fig 12 in
271259 # [Coleman-Li1994]
272260 logger .debug ('Newton direction did not have negative '
273- 'curvature adding scaling * np.sign(sg) to '
274- '2D subspace.' )
261+ 'using scaling * np.sign(sg) and ev to smallest '
262+ 'eigenvalue instead.' )
263+ e , v = slinalg .eigs (self .shess , k = 1 , which = 'SR' )
264+ s_newt = v [:, np .argmin (e )]
265+ normalize (s_newt )
275266 s_grad = scaling * np .sign (sg ) + (sg == 0 )
276267 else :
277268 s_grad = sg .copy ()
@@ -290,6 +281,78 @@ def __init__(self, x, sg, hess, scaling, g_dscaling, delta, theta,
290281 self .subspace = np .expand_dims (s_newt , 1 )
291282
292283
284+ class CGStep (Step ):
285+ """
286+ This class provides the machinery to compute an approximate solution of
287+ the trust region subproblem using the Steihaug Method
288+ """
289+
290+ type = 'cg'
291+
292+ def calculate (self ):
293+ nsg = norm (self .sg )
294+ self .conj_grad (min (0.5 , np .sqrt (nsg )) * nsg )
295+ self .s = self .scaling .dot (self .ss + self .ss0 )
296+ self .step_back ()
297+ self .qpval = quadratic_form (self .shess , self .sg , self .ss + self .ss0 )
298+
299+ def conj_grad (self , eps ):
300+ """
301+ Compute step proposal using conjugate gradient method
302+
303+ :param eps:
304+ tolerance for residual norm
305+ """
306+ raise NotImplementedError ()
307+
308+
309+ class TRStepSteihaug (CGStep ):
310+ """
311+ This class provides the machinery to compute an approximate solution of
312+ the trust region subproblem using the Steihaug Method
313+ """
314+
315+ type = 'cgs'
316+
317+ def conj_grad (self , eps ):
318+ z = np .zeros_like (self .sg )
319+ r = self .sg .copy ()
320+ d = - self .sg .copy ()
321+ if norm (r ) < eps :
322+ self .ss = z
323+ return
324+
325+ while True :
326+ bd = self .shess .dot (d )
327+ c = d .dot (bd )
328+ r2 = np .dot (r , r )
329+ alpha = r2 / c
330+ zp = z + alpha * d
331+ if c <= 0 or norm (zp ) >= self .delta :
332+ self .subspace = np .expand_dims (d , 1 )
333+ self .ss0 = z
334+ self .sc = get_1d_trust_region_boundary_solution (
335+ self .shess , self .sg , self .subspace [:, 0 ], self .ss0 ,
336+ self .delta
337+ ) * np .ones ((1 ,))
338+ self .ss = self .subspace .dot (self .sc )
339+ return
340+ rp = r + alpha * bd
341+ rp2 = np .dot (rp , rp )
342+ if np .sqrt (rp2 ) < eps :
343+ normalize (d )
344+ self .subspace = np .expand_dims (d , 1 )
345+ self .sc = zp .dot (d ) * np .ones ((1 ,))
346+ self .ss = self .subspace .dot (self .sc )
347+ self .ss0 = zp - self .ss
348+ return
349+ beta = rp2 / r2
350+
351+ d = - rp + beta * d
352+ z = zp
353+ r = rp
354+
355+
293356class TRStepReflected (Step ):
294357 """
295358 This class provides the machinery to compute a reflected step based on
@@ -374,6 +437,22 @@ def __init__(self, x, sg, hess, scaling, g_dscaling, delta, theta,
374437 self .subspace = np .expand_dims (s_grad , 1 )
375438
376439
440+ class ScaledGradientStep (Step ):
441+ """
442+ This class provides the machinery to compute a gradient step.
443+ """
444+
445+ type = 'dg'
446+
447+ def __init__ (self , x , sg , hess , scaling , g_dscaling , delta , theta ,
448+ ub , lb , logger ):
449+ super ().__init__ (x , sg , hess , scaling , g_dscaling , delta , theta ,
450+ ub , lb , logger )
451+ s_grad = scaling * sg .copy ()
452+ normalize (s_grad )
453+ self .subspace = np .expand_dims (s_grad , 1 )
454+
455+
377456class RefinedStep (Step ):
378457 """
379458 This class provides the machinery to refine a step based on interior
@@ -393,8 +472,8 @@ def __init__(self, x, sg, hess, scaling, g_dscaling, delta, theta,
393472 NonlinearConstraint (
394473 fun = lambda xs : (norm (xs ) - delta ) * np .ones ((1 ,)),
395474 jac = lambda xs : np .expand_dims (xs , 1 ).T / norm (xs ),
396- lb = np .zeros ((1 ,)),
397- ub = np .ones ((1 ,)) * np . inf ,
475+ lb = - np .ones ((1 ,)) * np . inf ,
476+ ub = np .zeros ((1 ,)),
398477 )
399478 ]
400479 self .guess = step .ss + step .ss0
0 commit comments