Coverage for C:\Users\t590r\Documents\GitHub\suppy\suppy\projections\_subgradient_projections.py: 85%

1"""Subgradient projections for feasibility algorithms."""

2 from typing import Callable, List

3 import numpy as np

4 import numpy.typing as npt

5

6 from suppy.projections._projections import BasicProjection

7

8 try:

9 import cupy as cp

10

11 NO_GPU = False

12 except ImportError:

13 NO_GPU = True

14 cp = np

15

16

17 class SubgradientProjection(BasicProjection):

18 """Projection using subgradients."""

19

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.

34

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.

45

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 []

55

56 def func_call(self, x):

57 """

58 Call the objective function.

59

60 Parameters:

61 - x (npt.NDArray): The input array.

62

63 Returns:

64 - float: The value of the objective function.

65 """

66 return self.func(x[self.idx], *self.func_args)

67

68 def grad_call(self, x):

69 """

70 Call the gradient function.

71

72 Parameters:

73 - x (npt.NDArray): The input array.

74

75 Returns:

76 - npt.NDArray: The gradient of the objective function.

77 """

78 return self.grad(x[self.idx], *self.grad_args)

79

80 def _project(self, x: npt.NDArray) -> npt.NDArray:

81 """

82 Project the input array onto the specified level.

83

84 Parameters:

85 - x (npt.NDArray): The input array.

86

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)

93

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

97

98 def level_diff(self, x: npt.NDArray) -> float:

99 """

100 Calculate the difference between the objective function value and

101 the set level.

102

103 Parameters:

104 - x (npt.NDArray): The input array.

105

106 Returns:

107 - float: The difference between the objective function value and the set level.

108 """

109 return self.func_call(x) - self.level

110

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

126

127

128 class EUDProjection(SubgradientProjection):

129 """

130 Class representing the EUDProjection.

131

132 This class inherits from the SubgradientProjection class

133 and implements the EUD (Equivalent Uniform Dose) projection.

134

135 Parameters:

136 - a (float): Exponent used in the EUD projection.

137 - level (int): The level of the projection.

138

139 Attributes:

140 - a (float): Exponent used in the EUD projection.

141

142 Methods:

143 - func_call(x): Computes the EUD projection function.

144 - grad_call(x): Computes the gradient of the EUD projection function.

145 """

146

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

167

168 def _func(self, x):

169 """

170 Computes the EUD projection function.

171

172 Parameters:

173 - x (npt.NDArray): The input array.

174

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)

179

180 def _grad(self, x):

181 """

182 Computes the gradient of the EUD projection function.

183

184 Parameters:

185 - x (npt.NDArray): The input array.

186

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 )

195

196

197 class 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

211

212 def func_call(self, x):

213 """

214 Call the objective function.

215

216 Parameters:

217 - x (npt.NDArray): The input array.

218

219 Returns:

220 - float: The value of the objective function.

221 """

222 return self.func(self.A @ x[self.idx], *self.func_args)

223

224 def grad_call(self, x):

225 """

226 Call the gradient function.

227

228 Parameters:

229 - x (npt.NDArray): The input array.

230

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