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

1"""Normal superiorization algorithm."""

2 from typing import List

3 import numpy as np

4 import numpy.typing as npt

5 from suppy.utils import ensure_float_array

6 from suppy.perturbations import Perturbation

7 from ._sup import FeasibilityPerturbation

8

9 try:

10 import cupy as cp

11 except ImportError:

12 cp = np

13

14

15 class 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)