Coverage for C:\Users\t590r\Documents\GitHub\suppy\suppy\superiorization\_standard_sup.py: 97%

99 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2025-02-05 10:12 +0100

1"""Normal superiorization algorithm.""" 

2from typing import List 

3import numpy as np 

4import numpy.typing as npt 

5from suppy.utils import ensure_float_array 

6from suppy.perturbations import Perturbation 

7from ._sup import FeasibilityPerturbation 

8 

9try: 

10 import cupy as cp 

11except ImportError: 

12 cp = np 

13 

14 

15class Superiorization(FeasibilityPerturbation): 

16 """ 

17 Superiorization algorithm for constrained optimization problems. 

18 

19 Parameters 

20 ---------- 

21 basic : Callable 

22 The underlying feasibility seeking algorithm. 

23 perturbation_scheme : Perturbation 

24 The perturbation scheme to be used for superiorization. 

25 

26 Attributes 

27 ---------- 

28 basic : Callable 

29 The underlying feasibility seeking algorithm. 

30 perturbation_scheme : Perturbation 

31 The perturbation scheme to be used for superiorization. 

32 objective_tol : float 

33 Tolerance for the objective function value change to determine stopping criteria. 

34 constr_tol : float 

35 Tolerance for the constraint proximity value change to determine stopping criteria. 

36 f_k : float 

37 The last value of the objective function. 

38 p_k : float 

39 The last value of the constraint function. 

40 _k : int 

41 The current iteration number. 

42 all_x : list | None 

43 List of all points achieved during the optimization process, only stored if requested by the user. 

44 all_function_values : list | None 

45 List of all objective function values achieved during the optimization process, only stored if requested by the user. 

46 all_x_function_reduction : list | None 

47 List of all points achieved via the function reduction step, only stored if requested by the user. 

48 all_function_values_function_reduction : list | None 

49 List of all objective function values achieved via the function reduction step, only stored if requested by the user. 

50 all_x_basic : list | None 

51 List of all points achieved via the basic feasibility seeking algorithm, only stored if requested by the user. 

52 all_function_values_basic : list | None 

53 List of all objective function values achieved via the basic feasibility seeking algorithm, only stored if requested by the user. 

54 """ 

55 

56 def __init__( 

57 self, 

58 basic, 

59 perturbation_scheme: Perturbation, 

60 ): 

61 

62 super().__init__(basic) 

63 self.perturbation_scheme = perturbation_scheme 

64 

65 # initialize some variables for the algorithms 

66 self.f_k = None 

67 self.p_k = None 

68 self._k = 0 

69 

70 self.all_x = [] 

71 self.all_function_values = [] # array storing all objective function values 

72 self.all_proximity_values = [] # array storing all proximity function values 

73 

74 self.all_x_function_reduction = ( 

75 [] 

76 ) # array storing all points achieved via the function reduction step 

77 self.all_function_values_function_reduction = ( 

78 [] 

79 ) # array storing all objective function values achieved via the function reduction step 

80 self.all_proximity_values_function_reduction = ( 

81 [] 

82 ) # array storing all proximity function values achieved via the function reduction step 

83 

84 self.all_x_basic = [] # array storing all points achieved via the basic algorithm 

85 self.all_function_values_basic = ( 

86 [] 

87 ) # array storing all objective function values achieved via the basic algorithm 

88 self.all_proximity_values_basic = ( 

89 [] 

90 ) # array storing all proximity function values achieved via the basic algorithm 

91 

92 @ensure_float_array 

93 def solve( 

94 self, 

95 x_0: npt.NDArray, 

96 max_iter: int = 10, 

97 storage=False, 

98 constr_tol: float = 1e-6, 

99 proximity_measures: List | None = None, 

100 objective_tol: float = 1e-6, 

101 ) -> npt.NDArray: 

102 """ 

103 Solve the optimization problem using the superiorization method. 

104 

105 Parameters 

106 ---------- 

107 x_0 : npt.NDArray 

108 Initial guess for the solution. 

109 max_iter : int, optional 

110 Maximum number of iterations to perform (default is 10). 

111 storage : bool, optional 

112 If True, store intermediate results (default is False). 

113 constr_tol : float, optional 

114 Tolerance for the constraint function value to determine stopping criteria, by default 1e-6. 

115 proximity_measures : List, optional 

116 The proximity measures to calculate, by default None. Right now only the first in the list is used to check the feasibility. 

117 objective_tol : float, optional 

118 Tolernace for the objective function value to determine stopping criteria, by default 1e-6. 

119 

120 Returns 

121 ------- 

122 npt.NDArray 

123 The optimized solution. 

124 """ 

125 if proximity_measures is None: 

126 proximity_measures = [("p_norm", 2)] 

127 else: 

128 # TODO: check that proximity measures are valid 

129 _ = None 

130 # initialization of variables 

131 x = x_0 

132 self._k = 0 # reset counter if necessary 

133 stop = False 

134 

135 # initial function and proximity values 

136 self.f_k = self.perturbation_scheme.func(x_0) 

137 self.p_k = self.basic.proximity(x_0, proximity_measures) 

138 

139 if storage: 

140 self._initial_storage(x_0, self.f_k, self.p_k) 

141 

142 while self._k < max_iter and not stop: 

143 self.perturbation_scheme.pre_step() 

144 # check if a restart should be performed 

145 

146 # perform the perturbation schemes update step 

147 x = self.perturbation_scheme.perturbation_step(x) 

148 

149 if storage: 

150 self._storage_function_reduction( 

151 x, 

152 self.perturbation_scheme.func(x), 

153 self.basic.proximity(x, proximity_measures), 

154 ) 

155 if self._k % 10 == 0: 

156 print(f"Current iteration: {self._k}") 

157 # perform basic step 

158 x = self.basic.step(x) 

159 

160 # check current function and proximity values 

161 f_temp = self.perturbation_scheme.func(x) 

162 p_temp = self.basic.proximity(x, proximity_measures) 

163 

164 if storage: 

165 self._storage_basic_step(x, f_temp, p_temp) 

166 

167 self._k += 1 

168 

169 # enable different stopping criteria for different superiorization algorithms 

170 stop = self._stopping_criteria(f_temp, p_temp, objective_tol, constr_tol) 

171 

172 # update function and proximity values 

173 self.f_k = f_temp 

174 self.p_k = p_temp 

175 

176 self._additional_action(x) 

177 

178 self._post_step(x) 

179 

180 return x 

181 

182 def _stopping_criteria( 

183 self, f_temp: float, p_temp: List[float], objective_tol: float, constr_tol: float 

184 ) -> bool: 

185 """ 

186 Determine if the stopping criteria for the optimization process are 

187 met. 

188 

189 Parameters 

190 ---------- 

191 f_temp : float 

192 The current value of the objective function. 

193 p_temp : List[float] 

194 The current proximity values to the constraints. 

195 objective_tol : float 

196 Tolerance for the objective function value change to determine stopping criteria. 

197 constr_tol : float 

198 Tolerance for the constraint proximity value change to determine stopping criteria. 

199 

200 Returns 

201 ------- 

202 bool 

203 True if the stopping criteria are met, False otherwise. 

204 """ 

205 stop = abs(f_temp - self.f_k) < objective_tol and p_temp[0] < constr_tol 

206 return stop 

207 

208 def _additional_action(self, x: npt.NDArray): 

209 """ 

210 Perform an additional action on the input, in case it is needed. 

211 

212 Parameters 

213 ---------- 

214 x : npt.NDArray 

215 The current iterate 

216 

217 Returns 

218 ------- 

219 None 

220 """ 

221 

222 def _initial_storage(self, x, f, p): 

223 """ 

224 Initializes storage for objective values and appends initial values. 

225 

226 Parameters 

227 ---------- 

228 x : array-like 

229 Initial values of the variables. 

230 f : array-like 

231 Initial values of the objective function. 

232 p : array-like 

233 Proximity function value 

234 """ 

235 # reset objective values 

236 self.all_x = [] 

237 self.all_function_values = [] # array storing all objective function values 

238 self.all_proximity_values = [] # array storing all proximity function values 

239 

240 self.all_x_function_reduction = [] 

241 self.all_function_values_function_reduction = [] 

242 self.all_proximity_values_function_reduction = [] 

243 

244 self.all_x_basic = [] 

245 self.all_function_values_basic = [] 

246 self.all_proximity_values_basic = [] 

247 

248 # append initial values 

249 self.all_x.append(x) 

250 self.all_function_values.append(f) 

251 self.all_proximity_values.append(p) 

252 

253 def _storage_function_reduction(self, x: npt.NDArray, f: float, p: float): 

254 """ 

255 Stores the given values of x and f into the corresponding lists. 

256 

257 Parameters 

258 ---------- 

259 x : npt.NDArray 

260 The current value of the variable x to be stored. 

261 f : float 

262 The current value of the function f to be stored. 

263 p : float 

264 The current value of the proximity function p to be stored. 

265 

266 Notes 

267 ----- 

268 This method appends the given values of x and f to the lists 

269 `all_x`, `all_function_values`, `all_x_function_reduction`, 

270 and `all_function_values_function_reduction`. 

271 """ 

272 self.all_x.append(x.copy()) 

273 self.all_function_values.append(f) 

274 self.all_x_function_reduction.append(x.copy()) 

275 self.all_function_values_function_reduction.append(f) 

276 self.all_proximity_values_function_reduction.append(p) 

277 self.all_proximity_values.append(p) 

278 

279 def _storage_basic_step(self, x: npt.NDArray, f: float, p: float): 

280 """ 

281 Stores the current values of x and f in the respective lists. 

282 

283 Parameters 

284 ---------- 

285 x : array-like 

286 The current value of the variable x. 

287 f : float 

288 The current value of the function f. 

289 p : float 

290 The current value of the proximity function p. 

291 

292 Notes 

293 ----- 

294 This method appends the current values of x and f to both the basic and 

295 general lists of x values and function values. 

296 """ 

297 self.all_x_basic.append(x.copy()) 

298 self.all_function_values_basic.append(f) 

299 self.all_x.append(x.copy()) 

300 self.all_function_values.append(f) 

301 self.all_proximity_values_basic.append(p) 

302 self.all_proximity_values.append(p) 

303 

304 def _post_step(self, x: npt.NDArray): 

305 """ 

306 Perform an action after the optimization process has finished. 

307 

308 Parameters 

309 ---------- 

310 x : array-like 

311 The current value of the variable x. 

312 

313 Returns 

314 ------- 

315 None 

316 """ 

317 xp = cp if isinstance(x, cp.ndarray) else np 

318 self.all_x = xp.array(self.all_x) 

319 self.all_function_values = xp.array(self.all_function_values) 

320 self.all_x_function_reduction = xp.array(self.all_x_function_reduction) 

321 self.all_function_values_function_reduction = xp.array( 

322 self.all_function_values_function_reduction 

323 ) 

324 self.all_x_basic = xp.array(self.all_x_basic) 

325 self.all_function_values_basic = xp.array(self.all_function_values_basic) 

326 self.all_proximity_values = xp.array(self.all_proximity_values) 

327 self.all_proximity_values_function_reduction = xp.array( 

328 self.all_proximity_values_function_reduction 

329 ) 

330 self.all_proximity_values_basic = xp.array(self.all_proximity_values_basic)