Coverage for C:\Users\t590r\Documents\GitHub\suppy\suppy\projections\_subgradient_projections.py: 85%
60 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"""Subgradient projections for feasibility algorithms."""
2from typing import Callable, List
3import numpy as np
4import numpy.typing as npt
6from suppy.projections._projections import BasicProjection
8try:
9 import cupy as cp
11 NO_GPU = False
12except ImportError:
13 NO_GPU = True
14 cp = np
17class SubgradientProjection(BasicProjection):
18 """Projection using subgradients."""
20 def __init__(
21 self,
22 func: Callable,
23 grad: Callable,
24 level: float = 0,
25 func_args: List | None = None,
26 grad_args: List | None = None,
27 relaxation: float = 1,
28 idx: npt.NDArray | None = None,
29 proximity_flag=True,
30 use_gpu=False,
31 ):
32 """
33 Initialize the SubgradientProjection object.
35 Parameters:
36 - func (Callable): The objective function.
37 - grad (Callable): The gradient function.
38 - level (float): The level at which to project.
39 - func_args (Any): Additional arguments for the objective function.
40 - grad_args (Any): Additional arguments for the gradient function.
41 - relaxation (float): The relaxation parameter.
42 - idx (npt.NDArray | None): The indices to project on.
43 - proximity_flag (bool): Flag to use proximity function.
44 - use_gpu (bool): Flag to show whether the function and gradient calls are performed on the GPU or not.
46 Returns:
47 - None
48 """
49 super().__init__(relaxation, idx, proximity_flag, _use_gpu=use_gpu)
50 self.func = func
51 self.grad = grad
52 self.level = level
53 self.func_args = func_args if func_args is not None else []
54 self.grad_args = grad_args if grad_args is not None else []
56 def func_call(self, x):
57 """
58 Call the objective function.
60 Parameters:
61 - x (npt.NDArray): The input array.
63 Returns:
64 - float: The value of the objective function.
65 """
66 return self.func(x[self.idx], *self.func_args)
68 def grad_call(self, x):
69 """
70 Call the gradient function.
72 Parameters:
73 - x (npt.NDArray): The input array.
75 Returns:
76 - npt.NDArray: The gradient of the objective function.
77 """
78 return self.grad(x[self.idx], *self.grad_args)
80 def _project(self, x: npt.NDArray) -> npt.NDArray:
81 """
82 Project the input array onto the specified level.
84 Parameters:
85 - x (npt.NDArray): The input array.
87 Returns:
88 - npt.NDArray: The projected array.
89 """
90 xp = cp if isinstance(x, cp.ndarray) else np
91 f_x = self.func_call(x)
92 g_x = self.grad_call(x)
94 if f_x > self.level and xp.linalg.norm(g_x) > 0:
95 x[self.idx] -= (f_x - self.level) * g_x / (g_x @ g_x)
96 return x
98 def level_diff(self, x: npt.NDArray) -> float:
99 """
100 Calculate the difference between the objective function value and
101 the set level.
103 Parameters:
104 - x (npt.NDArray): The input array.
106 Returns:
107 - float: The difference between the objective function value and the set level.
108 """
109 return self.func_call(x) - self.level
111 def _proximity(self, x: npt.NDArray, proximity_measures: List) -> float:
112 dist = self.level_diff(x)
113 dist = dist if dist > 0 else 0
114 measures = []
115 for measure in proximity_measures:
116 if isinstance(measure, tuple):
117 if measure[0] == "p_norm":
118 measures.append(dist ** measure[1])
119 else:
120 raise ValueError("Invalid proximity measure")
121 elif isinstance(measure, str) and measure == "max_norm":
122 measures.append(dist)
123 else:
124 raise ValueError("Invalid proximity measure")
125 return measures
128class EUDProjection(SubgradientProjection):
129 """
130 Class representing the EUDProjection.
132 This class inherits from the SubgradientProjection class
133 and implements the EUD (Equivalent Uniform Dose) projection.
135 Parameters:
136 - a (float): Exponent used in the EUD projection.
137 - level (int): The level of the projection.
139 Attributes:
140 - a (float): Exponent used in the EUD projection.
142 Methods:
143 - func_call(x): Computes the EUD projection function.
144 - grad_call(x): Computes the gradient of the EUD projection function.
145 """
147 def __init__(
148 self,
149 a: float,
150 EUD_max: float = 10,
151 relaxation: float = 1,
152 idx: npt.NDArray | None = None,
153 proximity_flag=True,
154 use_gpu=False,
155 ):
156 """Initializes the EUDProjection object."""
157 super().__init__(
158 self._func,
159 self._grad,
160 relaxation=relaxation,
161 level=EUD_max,
162 idx=idx,
163 proximity_flag=proximity_flag,
164 use_gpu=use_gpu,
165 )
166 self.a = a
168 def _func(self, x):
169 """
170 Computes the EUD projection function.
172 Parameters:
173 - x (npt.NDArray): The input array.
175 Returns:
176 - npt.NDArray: The result of the EUD projection function.
177 """
178 return (1 / x.shape[0] * ((x**self.a).sum(axis=0))) ** (1 / self.a)
180 def _grad(self, x):
181 """
182 Computes the gradient of the EUD projection function.
184 Parameters:
185 - x (npt.NDArray): The input array.
187 Returns:
188 - npt.NDArray: The gradient of the EUD projection function.
189 """
190 return (
191 ((x**self.a).sum()) ** (1 / self.a - 1)
192 * (x ** (self.a - 1))
193 / len(x) ** (1 / self.a)
194 )
197class WeightEUDProjection(EUDProjection):
198 def __init__(
199 self,
200 A: npt.NDArray,
201 a: float,
202 EUD_max: float = 10,
203 relaxation: float = 1,
204 idx: npt.NDArray | None = None,
205 proximity_flag=True,
206 use_gpu=False,
207 ):
208 """Initializes the EUDProjection object."""
209 super().__init__(a, EUD_max, idx, proximity_flag=proximity_flag, use_gpu=use_gpu)
210 self.A = A
212 def func_call(self, x):
213 """
214 Call the objective function.
216 Parameters:
217 - x (npt.NDArray): The input array.
219 Returns:
220 - float: The value of the objective function.
221 """
222 return self.func(self.A @ x[self.idx], *self.func_args)
224 def grad_call(self, x):
225 """
226 Call the gradient function.
228 Parameters:
229 - x (npt.NDArray): The input array.
231 Returns:
232 - npt.NDArray: The gradient of the objective function.
233 """
234 return (self.A).T @ (self.grad(self.A @ x[self.idx], *self.grad_args))