Coverage for C:\Users\t590r\Documents\GitHub\suppy\suppy\feasibility\_hyperplanes\_ams_algorithms.py: 75%
148 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
1from abc import ABC
2from typing import List
3import numpy as np
4import numpy.typing as npt
6try:
7 import cupy as cp
9 NO_GPU = False
11except ImportError:
12 NO_GPU = True
13 cp = np
15from suppy.feasibility._linear_algorithms import HyperplaneFeasibility
16from suppy.utils import LinearMapping
19class HyperplaneAMSAlgorithm(HyperplaneFeasibility, ABC):
20 """
21 The HyperplaneAMSAlgorithm class is used to find a feasible solution to
22 a
23 set of linear inequalities.
25 Parameters
26 ----------
27 A : npt.NDArray
28 The matrix representing the coefficients of the linear inequalities.
29 b : npt.NDArray
30 Bound for linear inequalities
31 algorithmic_relaxation : npt.NDArray or float, optional
32 The relaxation parameter for the algorithm, by default 1.
33 relaxation : float, optional
34 The relaxation parameter for the feasibility problem, by default 1.
35 proximity_flag : bool, optional
36 A flag indicating whether to use proximity in the algorithm, by default True.
37 """
39 def __init__(
40 self,
41 A: npt.NDArray,
42 b: npt.NDArray,
43 algorithmic_relaxation: npt.NDArray | float = 1,
44 relaxation: float = 1,
45 proximity_flag: bool = True,
46 ):
47 super().__init__(A, b, algorithmic_relaxation, relaxation, proximity_flag)
50class SequentialAMSHyperplane(HyperplaneAMSAlgorithm):
51 """
52 SequentialAMS class for sequentially applying the AMS algorithm.
54 Parameters
55 ----------
56 A : npt.NDArray
57 The matrix A used in the AMS algorithm.
58 b : npt.NDArray
59 Bound for linear inequalities
60 algorithmic_relaxation : npt.NDArray or float, optional
61 The relaxation parameter for the algorithm, by default 1.
62 relaxation : float, optional
63 The relaxation parameter, by default 1.
64 cs : None or List[int], optional
65 The list of indices for the constraints, by default None.
66 proximity_flag : bool, optional
67 Flag to indicate if proximity should be considered, by default True.
69 Attributes
70 ----------
71 """
73 def __init__(
74 self,
75 A: npt.NDArray,
76 b: npt.NDArray,
77 algorithmic_relaxation: npt.NDArray | float = 1,
78 relaxation: float = 1,
79 cs: None | List[int] = None,
80 proximity_flag: bool = True,
81 ):
83 super().__init__(A, b, algorithmic_relaxation, relaxation, proximity_flag)
84 xp = cp if self._use_gpu else np
85 if cs is None:
86 self.cs = xp.arange(self.A.shape[0])
87 else:
88 self.cs = cs
90 def _project(self, x: npt.NDArray) -> npt.NDArray:
91 """
92 Projects the input array `x` onto the feasible region defined by the
93 constraints.
95 Parameters
96 ----------
97 x : npt.NDArray
98 The input array to be projected.
100 Returns
101 -------
102 npt.NDArray
103 The projected array.
104 """
106 for i in self.cs:
107 p_i = self.single_map(x, i)
108 res = self.b[i] - p_i
109 self.A.update_step(x, self.algorithmic_relaxation * self.inverse_row_norm[i] * res, i)
110 return x
113class SequentialWeightedAMSHyperplane(SequentialAMSHyperplane):
114 """
115 Parameters
116 ----------
117 A : npt.NDArray
118 The constraint matrix.
119 b : npt.NDArray
120 Bound for linear inequalities
121 weights : None, list of float, or npt.NDArray, optional
122 The weights assigned to each constraint. If None, default weights are
123 used.
124 algorithmic_relaxation : npt.NDArray or float, optional
125 The relaxation parameter for the algorithm. Default is 1.
126 relaxation : float, optional
127 The relaxation parameter for the algorithm. Default is 1.
128 weight_decay : float, optional
129 Parameter that determines the rate at which the weights are reduced
130 after each phase (weights * weight_decay). Default is 1.
131 cs : None or list of int, optional
132 The indices of the constraints to be considered. Default is None.
133 proximity_flag : bool, optional
134 Flag to indicate if proximity should be considered. Default is True.
136 Attributes
137 ----------
138 weights : npt.NDArray
139 The weights assigned to each constraint.
140 weight_decay : float
141 Decay rate for the weights.
142 temp_weight_decay : float
143 Initial value for weight decay.
144 """
146 def __init__(
147 self,
148 A: npt.NDArray,
149 b: npt.NDArray,
150 weights: None | List[float] | npt.NDArray = None,
151 algorithmic_relaxation: npt.NDArray | float = 1,
152 relaxation: float = 1,
153 weight_decay: float = 1,
154 cs: None | List[int] = None,
155 proximity_flag: bool = True,
156 ):
158 super().__init__(A, b, algorithmic_relaxation, relaxation, cs, proximity_flag)
159 xp = cp if self._use_gpu else np
160 self.weight_decay = weight_decay # decay rate
161 self.temp_weight_decay = 1 # initial value for weight decay
163 if weights is None:
164 self.weights = xp.ones(self.A.shape[0])
165 elif xp.abs((weights.sum() - 1)) > 1e-10:
166 print("Weights do not add up to 1! Renormalizing to 1...")
167 self.weights = weights
169 def _project(self, x: npt.NDArray) -> npt.NDArray:
170 """
171 Projects the input array `x` onto a feasible region defined by the
172 constraints.
174 Parameters
175 ----------
176 x : npt.NDArray
177 The input array to be projected.
179 Returns
180 -------
181 npt.NDArray
182 The projected array.
184 Notes
185 -----
186 This method iteratively adjusts the input array `x` based on the constraints
187 defined in `self.cs`. For each constraint, it computes the projection and
188 checks if the constraints are violated. If a constraint is violated, it updates
189 the array `x` using a weighted relaxation factor. The weight decay is applied
190 to the temporary weight decay after each iteration.
191 """
193 weighted_relaxation = self.algorithmic_relaxation * self.temp_weight_decay
195 for i in self.cs:
196 p_i = self.single_map(x, i)
197 res = self.b[i] - p_i
198 self.A.update_step(
199 x, weighted_relaxation * self.weights[i] * self.inverse_row_norm[i] * res, i
200 )
202 self.temp_weight_decay *= self.weight_decay
203 return x
206class SimultaneousAMSHyperplane(HyperplaneAMSAlgorithm):
207 """
208 SimultaneousAMS is an implementation of the AMS (Alternating
209 Minimization Scheme) algorithm
210 that performs simultaneous projections and proximity calculations.
212 Parameters
213 ----------
214 A : npt.NDArray
215 The matrix representing the constraints.
216 b : npt.NDArray
217 Bound for linear inequalities
218 algorithmic_relaxation : npt.NDArray or float, optional
219 The relaxation parameter for the algorithm, by default 1.
220 relaxation : float, optional
221 The relaxation parameter for the projections, by default 1.
222 weights : None or List[float], optional
223 The weights for the constraints, by default None.
224 proximity_flag : bool, optional
225 Flag to indicate if proximity calculations should be performed, by default True.
226 """
228 def __init__(
229 self,
230 A: npt.NDArray,
231 b: npt.NDArray,
232 algorithmic_relaxation: npt.NDArray | float = 1,
233 relaxation: float = 1,
234 weights: None | List[float] = None,
235 proximity_flag: bool = True,
236 ):
238 super().__init__(A, b, algorithmic_relaxation, relaxation, proximity_flag)
240 xp = cp if self._use_gpu else np
242 if weights is None:
243 self.weights = xp.ones(self.A.shape[0]) / self.A.shape[0]
244 elif xp.abs((weights.sum() - 1)) > 1e-10:
245 print("Weights do not add up to 1! Renormalizing to 1...")
246 self.weights = weights / weights.sum()
247 else:
248 self.weights = weights
250 def _project(self, x):
251 # simultaneous projection
252 p = self.map(x)
253 res = self.b - p
254 x += self.algorithmic_relaxation * (self.weights * self.inverse_row_norm * res @ self.A)
255 return x
257 def _proximity(self, x: npt.NDArray, proximity_measures: List) -> float:
258 p = self.map(x)
259 # residuals are positive if constraints are met
260 res = abs(self.b - p)
261 measures = []
262 for measure in proximity_measures:
263 if isinstance(measure, tuple):
264 if measure[0] == "p_norm":
265 measures.append(self.weights @ (res ** measure[1]))
266 else:
267 raise ValueError("Invalid proximity measure")
268 elif isinstance(measure, str) and measure == "max_norm":
269 measures.append(res.max())
270 else:
271 raise ValueError("Invalid proximity measure")
272 return measures
275class ExtrapolatedLandweberHyperplane(SimultaneousAMSHyperplane):
276 def __init__(
277 self, A, b, algorithmic_relaxation=1, relaxation=1, weights=None, proximity_flag=True
278 ):
279 super().__init__(A, b, algorithmic_relaxation, relaxation, weights, proximity_flag)
280 self.a_i = self.A.row_norm(2, 2)
281 self.weight_norm = self.weights / self.a_i
282 self.sigmas = []
284 def _project(self, x):
285 p = self.map(x)
286 res = self.b - p
287 res_idx = res != 0
288 if not (np.any(res_idx)):
289 self.sigmas.append(0)
290 return x
291 t = self.weight_norm * res
292 t_2 = t @ self.A
293 sig = (res @ t) / (t_2 @ t_2)
294 self.sigmas.append(sig)
295 x += sig * t_2
297 return x
300class BlockIterativeAMSHyperplane(HyperplaneAMSAlgorithm):
301 """
302 Block Iterative AMS Algorithm.
303 This class implements a block iterative version of the AMS (Alternating
304 Minimization Scheme) algorithm.
305 It is designed to handle constraints and weights in a block-wise manner.
307 Parameters
308 ----------
309 A : npt.NDArray
310 The matrix representing the linear constraints.
311 b : npt.NDArray
312 Bound for linear inequalities
313 weights : List[List[float]] or List[npt.NDArray]
314 A list of lists or arrays representing the weights for each block. Each list/array should sum to 1.
315 algorithmic_relaxation : npt.NDArray or float, optional
316 The relaxation parameter for the algorithm, by default 1.
317 relaxation : float, optional
318 The relaxation parameter for the constraints, by default 1.
319 proximity_flag : bool, optional
320 A flag indicating whether to use proximity measures, by default True.
322 Raises
323 ------
324 ValueError
325 If any of the weight lists do not sum to 1.
326 """
328 def __init__(
329 self,
330 A: npt.NDArray,
331 b: npt.NDArray,
332 weights: List[List[float]] | List[npt.NDArray],
333 algorithmic_relaxation: npt.NDArray | float = 1,
334 relaxation: float = 1,
335 proximity_flag: bool = True,
336 ):
338 super().__init__(A, b, algorithmic_relaxation, relaxation, proximity_flag)
340 xp = cp if self._use_gpu else np
342 # check that weights is a list of lists that add up to 1 each
343 for el in weights:
344 if xp.abs((xp.sum(el) - 1)) > 1e-10:
345 raise ValueError("Weights do not add up to 1!")
347 self.weights = []
348 self.block_idxs = [
349 xp.where(xp.array(el) > 0)[0] for el in weights
350 ] # get idxs that meet requirements
352 # assemble a list of general weights
353 self.total_weights = xp.zeros_like(weights[0])
354 for el in weights:
355 el = xp.asarray(el)
356 self.weights.append(el[xp.array(el) > 0]) # remove non zero weights
357 self.total_weights += el / len(weights)
359 def _project(self, x):
360 # simultaneous projection
362 for el, block_idx in zip(self.weights, self.block_idxs): # get mask and associated weights
363 p = self.indexed_map(x, block_idx)
364 res = self.b[block_idx] - p
366 x += self.algorithmic_relaxation * (
367 el * self.inverse_row_norm[block_idx] * res @ self.A[block_idx, :]
368 )
369 return x
371 def _proximity(self, x: npt.NDArray, proximity_measures: List) -> float:
372 p = self.map(x)
373 # residuals are positive if constraints are met
374 res = abs(self.b - p)
375 measures = []
376 for measure in proximity_measures:
377 if isinstance(measure, tuple):
378 if measure[0] == "p_norm":
379 measures.append(self.total_weights @ (res ** measure[1]))
380 else:
381 raise ValueError("Invalid proximity measure")
382 elif isinstance(measure, str) and measure == "max_norm":
383 measures.append(res.max())
384 else:
385 raise ValueError("Invalid proximity measure")
386 return measures
389class StringAveragedAMSHyperplane(HyperplaneAMSAlgorithm):
391 """
392 StringAveragedAMS is an implementation of the HyperplaneAMSAlgorithm
393 that
394 performs
395 string averaged projections.
397 Parameters
398 ----------
399 A : npt.NDArray
400 The matrix A used in the algorithm.
401 b : npt.NDArray
402 Bound for linear inequalities
403 strings : List[List[int]]
404 A list of lists, where each inner list represents a string of indices.
405 algorithmic_relaxation : npt.NDArray or float, optional
406 The relaxation parameter for the algorithm, by default 1.
407 relaxation : float, optional
408 The relaxation parameter for the projection, by default 1.
409 weights : None or List[float], optional
410 The weights for each string, by default None. If None, equal weights are assigned.
411 proximity_flag : bool, optional
412 A flag indicating whether to use proximity, by default True.
413 """
415 def __init__(
416 self,
417 A: npt.NDArray,
418 b: npt.NDArray,
419 strings: List[List[int]],
420 algorithmic_relaxation: npt.NDArray | float = 1,
421 relaxation: float = 1,
422 weights: None | List[float] = None,
423 proximity_flag: bool = True,
424 ):
426 super().__init__(A, b, algorithmic_relaxation, relaxation, proximity_flag)
427 xp = cp if self._use_gpu else np
428 self.strings = strings
429 if weights is None:
430 self.weights = xp.ones(len(strings)) / len(strings)
432 # if check_weight_validity(weights):
433 # self.weights = weights
434 else:
435 if len(weights) != len(self.strings):
436 raise ValueError("The number of weights must be equal to the number of strings.")
438 self.weights = weights
439 # print('Choosing default weight vector...')
440 # self.weights = np.ones(self.A.shape[0])/self.A.shape[0]
442 def _project(self, x):
443 # string averaged projection
444 x_c = x.copy() # create a general copy of x
445 x -= x # reset x is this viable?
446 for string, weight in zip(self.strings, self.weights):
447 x_s = x_c.copy() # generate a copy for individual strings
448 for i in string:
449 p_i = self.single_map(x_s, i)
450 res_i = self.b[i] - p_i
451 self.A.update_step(
452 x_s, self.algorithmic_relaxation * self.inverse_row_norm[i] * res_i, i
453 )
454 x += weight * x_s
455 return x