|
1 | 1 | import numpy as np |
2 | 2 |
|
3 | 3 | def ralgb5(calcfg, x0, tolx=1e-12, tolg=1e-12, tolf=1e-12, maxiter=2000, alpha=2.3, nsims=30, h0=1, nh=3, q1=0.9, q2=1.1): |
4 | | - |
5 | | - m = len(x0) |
6 | | - hs = h0 |
7 | | - B = np.eye(m) |
8 | | - vf = np.zeros(nsims) + float('inf') |
9 | | - w = 1./alpha - 1 |
10 | | - |
11 | | - x = np.copy(x0) |
12 | | - xr = np.copy(x0) |
13 | | - |
14 | | - nit = 0 |
15 | | - ncalls = 1 |
16 | | - fr, g0 = calcfg(xr) |
17 | | - |
| 4 | + """ |
| 5 | + Subgradient method ralgb5 for minimizing convex functions with ravine-like structure. |
| 6 | +
|
| 7 | + This function implements the ralgb5 algorithm, which is a subgradient method with adaptive stepsize control |
| 8 | + and space dilation. It is particularly effective for minimizing non-smooth convex functions or smooth convex |
| 9 | + functions with ravine-like level surfaces. |
| 10 | +
|
| 11 | + Parameters: |
| 12 | + ----------- |
| 13 | + calcfg : function |
| 14 | + A function that computes the value of the objective function and its subgradient at a given point x. |
| 15 | + The function should return a tuple (f, g), where f is the function value and g is the subgradient. |
| 16 | +
|
| 17 | + x0 : numpy.ndarray |
| 18 | + The initial starting point for the optimization algorithm. It should be a 1D array of length n, where n |
| 19 | + is the number of variables. |
| 20 | +
|
| 21 | + tolx : float, optional |
| 22 | + Tolerance for stopping based on the change in the argument (default is 1e-12). The algorithm stops if |
| 23 | + the norm of the change in x between iterations is less than tolx. |
| 24 | +
|
| 25 | + tolg : float, optional |
| 26 | + Tolerance for stopping based on the norm of the subgradient (default is 1e-12). The algorithm stops if |
| 27 | + the norm of the subgradient is less than tolg. |
| 28 | +
|
| 29 | + tolf : float, optional |
| 30 | + Tolerance for stopping based on the change in the function value (default is 1e-12). The algorithm stops |
| 31 | + if the relative change in the function value over the last nsims iterations is less than tolf. |
| 32 | +
|
| 33 | + maxiter : int, optional |
| 34 | + Maximum number of iterations allowed (default is 2000). The algorithm stops if this number of iterations |
| 35 | + is reached. |
| 36 | +
|
| 37 | + alpha : float, optional |
| 38 | + Space dilation coefficient (default is 2.3). This parameter controls the rate at which the space is |
| 39 | + stretched in the direction of the subgradient difference. |
| 40 | +
|
| 41 | + nsims : int, optional |
| 42 | + Number of iterations to consider for the stopping criterion based on the change in the function value |
| 43 | + (default is 30). |
| 44 | +
|
| 45 | + h0 : float, optional |
| 46 | + Initial step size (default is 1). This is the starting step size for the line search along the |
| 47 | + subgradient direction. |
| 48 | +
|
| 49 | + nh : int, optional |
| 50 | + Number of steps after which the step size is increased (default is 3). The step size is increased by a |
| 51 | + factor of q2 every nh steps. |
| 52 | +
|
| 53 | + q1 : float, optional |
| 54 | + Coefficient for decreasing the step size (default is 0.9). If the line search completes in one step, the |
| 55 | + step size is multiplied by q1. |
| 56 | +
|
| 57 | + q2 : float, optional |
| 58 | + Coefficient for increasing the step size (default is 1.1). The step size is multiplied by q2 every nh |
| 59 | + steps. |
| 60 | +
|
| 61 | + Returns: |
| 62 | + -------- |
| 63 | + xr : numpy.ndarray |
| 64 | + The best approximation to the minimum point found by the algorithm. |
| 65 | +
|
| 66 | + fr : float |
| 67 | + The value of the objective function at the point xr. |
| 68 | +
|
| 69 | + nit : int |
| 70 | + The number of iterations performed. |
| 71 | +
|
| 72 | + ncalls : int |
| 73 | + The number of calls to the calcfg function. |
| 74 | +
|
| 75 | + ccode : int |
| 76 | + A code indicating the reason for stopping: |
| 77 | + - 1: Stopped based on the change in the function value (tolf). |
| 78 | + - 2: Stopped based on the norm of the subgradient (tolg). |
| 79 | + - 3: Stopped based on the change in the argument (tolx). |
| 80 | + - 4: Maximum number of iterations reached (maxiter). |
| 81 | + - 5: Emergency stop due to too many steps in the line search. |
| 82 | +
|
| 83 | + Notes: |
| 84 | + ------ |
| 85 | + The algorithm is based on the r-algorithm proposed by N.Z. Shor, with modifications for adaptive stepsize |
| 86 | + control and space dilation. It is particularly effective for minimizing non-smooth convex functions or |
| 87 | + smooth convex functions with ravine-like level surfaces. |
| 88 | +
|
| 89 | + References: |
| 90 | + ----------- |
| 91 | + [1] Stetsyuk, P.I. (2017). Subgradient methods ralgb5 and ralgb4 for minimization of ravine-like convex functions. |
| 92 | + [2] Shor, N.Z. (1979). Minimization Methods for Non-Differentiable Functions. Kiev: Naukova Dumka. |
| 93 | + """ |
| 94 | + |
| 95 | + # Initialize variables |
| 96 | + m = len(x0) # Number of variables |
| 97 | + hs = h0 # Current step size |
| 98 | + B = np.eye(m) # Transformation matrix (initially identity) |
| 99 | + vf = np.zeros(nsims) + float('inf') # Array to store function values for stopping criterion |
| 100 | + w = 1./alpha - 1 # Coefficient for space dilation |
| 101 | + |
| 102 | + x = np.copy(x0) # Current point |
| 103 | + xr = np.copy(x0) # Best point found so far |
| 104 | + |
| 105 | + nit = 0 # Iteration counter |
| 106 | + ncalls = 1 # Counter for function evaluations |
| 107 | + fr, g0 = calcfg(xr) # Initial function value and subgradient |
| 108 | + |
| 109 | + # Check if the initial subgradient is already small enough |
18 | 110 | if np.linalg.norm(g0) < tolg: |
19 | | - ccode = 2 |
| 111 | + ccode = 2 # Stopping code: subgradient norm is below tolerance |
20 | 112 | return xr, fr, nit, ncalls, ccode |
21 | 113 |
|
| 114 | + # Main optimization loop |
22 | 115 | while nit <= maxiter: |
23 | | - vf[nsims-1] = fr |
| 116 | + vf[nsims-1] = fr # Store the current function value |
24 | 117 |
|
| 118 | + # Compute the direction of movement in the transformed space |
25 | 119 | g1 = B.T @ g0 |
26 | 120 | dx = B @ (g1 / np.linalg.norm(g1)) |
27 | 121 | normdx = np.linalg.norm(dx) |
28 | 122 |
|
29 | | - d = 1 |
30 | | - cal = 0 |
31 | | - deltax = 0 |
| 123 | + d = 1 # Initialize the inner loop condition |
| 124 | + cal = 0 # Counter for steps in the line search |
| 125 | + deltax = 0 # Accumulated change in x during the line search |
32 | 126 |
|
| 127 | + # Line search along the subgradient direction |
33 | 128 | while d > 0 and cal <= 500: |
34 | | - x = x - hs*dx |
35 | | - deltax = deltax + hs * normdx |
| 129 | + x = x - hs*dx # Update the current point |
| 130 | + deltax = deltax + hs * normdx # Accumulate the change in x |
36 | 131 |
|
37 | | - ncalls += 1 |
38 | | - f, g1 = calcfg(x) |
| 132 | + ncalls += 1 # Increment the function evaluation counter |
| 133 | + f, g1 = calcfg(x) # Evaluate the function and subgradient at the new point |
39 | 134 |
|
| 135 | + # Update the best point found so far |
40 | 136 | if f < fr: |
41 | 137 | fr = f |
42 | 138 | xr = x |
43 | 139 |
|
| 140 | + # Check if the subgradient norm is below tolerance |
44 | 141 | if np.linalg.norm(g1) < tolg: |
45 | | - ccode = 2 |
| 142 | + ccode = 2 # Stopping code: subgradient norm is below tolerance |
46 | 143 | return xr, fr, nit, ncalls, ccode |
47 | 144 |
|
| 145 | + # Increase the step size every nh steps |
48 | 146 | if np.mod(cal, nh) == 0: |
49 | 147 | hs = hs * q2 |
| 148 | + |
| 149 | + # Check the inner loop condition |
50 | 150 | d = dx @ g1 |
51 | 151 |
|
52 | | - cal += 1 |
| 152 | + cal += 1 # Increment the line search step counter |
53 | 153 |
|
| 154 | + # Emergency stop if too many steps are taken in the line search |
54 | 155 | if cal > 500: |
55 | | - ccode = 5 |
| 156 | + ccode = 5 # Stopping code: emergency stop |
56 | 157 | return xr, fr, nit, ncalls, ccode |
57 | 158 |
|
| 159 | + # Decrease the step size if the line search completes in one step |
58 | 160 | if cal == 1: |
59 | 161 | hs = hs * q1 |
60 | 162 |
|
| 163 | + # Check if the change in x is below tolerance |
61 | 164 | if deltax < tolx: |
62 | | - ccode = 3 |
| 165 | + ccode = 3 # Stopping code: change in x is below tolerance |
63 | 166 | return xr, fr, nit, ncalls, ccode |
64 | 167 |
|
| 168 | + # Update the transformation matrix B and the subgradient |
65 | 169 | dg = B.T @ (g1 - g0) |
66 | 170 | xi = dg / np.linalg.norm(dg) |
67 | | - |
68 | 171 | B = B + w * np.outer((B @ xi), xi) |
69 | 172 | g0 = g1 |
70 | 173 |
|
| 174 | + # Update the function value history for the stopping criterion |
71 | 175 | vf = np.roll(vf, 1) |
72 | 176 | vf[0] = abs(fr - vf[0]) |
73 | 177 |
|
| 178 | + # Compute the relative change in the function value |
74 | 179 | if abs(fr) > 1: |
75 | 180 | deltaf = np.sum(vf)/abs(fr) |
76 | 181 | else: |
77 | 182 | deltaf = np.sum(vf) |
78 | 183 |
|
| 184 | + # Check if the relative change in the function value is below tolerance |
79 | 185 | if deltaf < tolf: |
80 | | - ccode = 1 |
| 186 | + ccode = 1 # Stopping code: change in function value is below tolerance |
81 | 187 | return xr, fr, nit, ncalls, ccode |
82 | 188 |
|
83 | | - nit += 1 |
| 189 | + nit += 1 # Increment the iteration counter |
84 | 190 |
|
85 | | - ccode=4 |
| 191 | + # If the maximum number of iterations is reached |
| 192 | + ccode = 4 # Stopping code: maximum number of iterations reached |
86 | 193 | return xr, fr, nit, ncalls, ccode |
0 commit comments