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

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

2from typing import Callable, List 

3import numpy as np 

4import numpy.typing as npt 

5 

6from suppy.projections._projections import BasicProjection 

7 

8try: 

9 import cupy as cp 

10 

11 NO_GPU = False 

12except ImportError: 

13 NO_GPU = True 

14 cp = np 

15 

16 

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

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

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 

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