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
« 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
9try:
10 import cupy as cp
11except ImportError:
12 cp = np
15class Superiorization(FeasibilityPerturbation):
16 """
17 Superiorization algorithm for constrained optimization problems.
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.
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 """
56 def __init__(
57 self,
58 basic,
59 perturbation_scheme: Perturbation,
60 ):
62 super().__init__(basic)
63 self.perturbation_scheme = perturbation_scheme
65 # initialize some variables for the algorithms
66 self.f_k = None
67 self.p_k = None
68 self._k = 0
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
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
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
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.
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.
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
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)
139 if storage:
140 self._initial_storage(x_0, self.f_k, self.p_k)
142 while self._k < max_iter and not stop:
143 self.perturbation_scheme.pre_step()
144 # check if a restart should be performed
146 # perform the perturbation schemes update step
147 x = self.perturbation_scheme.perturbation_step(x)
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)
160 # check current function and proximity values
161 f_temp = self.perturbation_scheme.func(x)
162 p_temp = self.basic.proximity(x, proximity_measures)
164 if storage:
165 self._storage_basic_step(x, f_temp, p_temp)
167 self._k += 1
169 # enable different stopping criteria for different superiorization algorithms
170 stop = self._stopping_criteria(f_temp, p_temp, objective_tol, constr_tol)
172 # update function and proximity values
173 self.f_k = f_temp
174 self.p_k = p_temp
176 self._additional_action(x)
178 self._post_step(x)
180 return x
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.
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.
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
208 def _additional_action(self, x: npt.NDArray):
209 """
210 Perform an additional action on the input, in case it is needed.
212 Parameters
213 ----------
214 x : npt.NDArray
215 The current iterate
217 Returns
218 -------
219 None
220 """
222 def _initial_storage(self, x, f, p):
223 """
224 Initializes storage for objective values and appends initial values.
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
240 self.all_x_function_reduction = []
241 self.all_function_values_function_reduction = []
242 self.all_proximity_values_function_reduction = []
244 self.all_x_basic = []
245 self.all_function_values_basic = []
246 self.all_proximity_values_basic = []
248 # append initial values
249 self.all_x.append(x)
250 self.all_function_values.append(f)
251 self.all_proximity_values.append(p)
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.
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.
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)
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.
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.
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)
304 def _post_step(self, x: npt.NDArray):
305 """
306 Perform an action after the optimization process has finished.
308 Parameters
309 ----------
310 x : array-like
311 The current value of the variable x.
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)