Coverage for geometric_kernels/kernels/matern_kernel.py: 79%

92 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-16 21:43 +0000

1""" 

2Provides :class:`MaternGeometricKernel`,the geometric Matérn kernel---with 

3the heat kernel as a special case---that just works. 

4 

5It wraps around different kernels and feature maps, dispatching on the space. 

6 

7Unless you know exactly what you are doing, use :class:`MaternGeometricKernel`. 

8""" 

9 

10from plum import dispatch, overload 

11 

12from geometric_kernels.feature_maps import ( 

13 DeterministicFeatureMapCompact, 

14 HodgeDeterministicFeatureMapCompact, 

15 RandomPhaseFeatureMapCompact, 

16 RandomPhaseFeatureMapNoncompact, 

17 RejectionSamplingFeatureMapHyperbolic, 

18 RejectionSamplingFeatureMapSPD, 

19) 

20from geometric_kernels.kernels.base import BaseGeometricKernel 

21from geometric_kernels.kernels.feature_map import MaternFeatureMapKernel 

22from geometric_kernels.kernels.hodge_compositional import MaternHodgeCompositionalKernel 

23from geometric_kernels.kernels.karhunen_loeve import MaternKarhunenLoeveKernel 

24from geometric_kernels.spaces import ( 

25 CompactMatrixLieGroup, 

26 DiscreteSpectrumSpace, 

27 Graph, 

28 GraphEdges, 

29 HodgeDiscreteSpectrumSpace, 

30 Hyperbolic, 

31 HypercubeGraph, 

32 Hypersphere, 

33 Mesh, 

34 NoncompactSymmetricSpace, 

35 Space, 

36 SymmetricPositiveDefiniteMatrices, 

37) 

38 

39 

40def default_feature_map( 

41 *, space: Space = None, num: int = None, kernel: BaseGeometricKernel = None 

42): 

43 """ 

44 Constructs the default feature map for the specified space or kernel. 

45 

46 :param space: 

47 A space to construct the feature map on. If provided, kernel must 

48 either be omitted or set to None. 

49 :param kernel: 

50 A kernel to construct the feature map from. If provided, `space` and 

51 `num` must either be omitted or set to None. 

52 :param num: 

53 Controls the number of features (dimensionality of the feature 

54 map). If omitted or set to None, the default value for each 

55 respective space is used. Must only be provided when 

56 constructing a feature map on a space (not from a kernel). 

57 

58 :return: 

59 Callable which is the respective feature map. 

60 """ 

61 if kernel is not None: 

62 if space is not None or num is not None: 

63 raise ValueError( 

64 "When kernel is provided, space and num must be omitted or set to None" 

65 ) 

66 return feature_map_from_kernel(kernel) # type: ignore[call-overload] 

67 elif space is not None: 

68 if num is None: 

69 num = default_num(space) # type: ignore[call-overload] 

70 return feature_map_from_space(space, num) # type: ignore[call-overload] 

71 else: 

72 raise ValueError( 

73 "Either kernel or space must be provided and be different from None" 

74 ) 

75 

76 

77@overload 

78def feature_map_from_kernel(kernel: MaternKarhunenLoeveKernel): 

79 if isinstance(kernel.space, CompactMatrixLieGroup): 

80 # Because `CompactMatrixLieGroup` does not currently support explicit 

81 # eigenfunction computation (they only support addition theorem). 

82 return RandomPhaseFeatureMapCompact( 

83 kernel.space, 

84 kernel.num_levels, 

85 MaternGeometricKernel._DEFAULT_NUM_RANDOM_PHASES, 

86 ) 

87 else: 

88 return DeterministicFeatureMapCompact(kernel.space, kernel.num_levels) 

89 

90 

91@overload 

92def feature_map_from_kernel(kernel: MaternHodgeCompositionalKernel): 

93 return HodgeDeterministicFeatureMapCompact(kernel.space, kernel.num_levels) 

94 

95 

96@overload 

97def feature_map_from_kernel(kernel: MaternFeatureMapKernel): 

98 return kernel.feature_map 

99 

100 

101@dispatch 

102def feature_map_from_kernel(kernel: BaseGeometricKernel): 

103 """ 

104 Return the default feature map for the specified kernel `kernel`. 

105 

106 :param kernel: 

107 A kernel to construct the feature map from. 

108 

109 :return: 

110 A feature map. 

111 :rtype: feature_maps.FeatureMap 

112 

113 .. note:: 

114 This function is organized as an abstract dispatcher plus a set of 

115 @overload-decorated implementations, one for each type of kernels. 

116 

117 When followed by an "empty" @dispatch-decorated function of the same 

118 name, plum-dispatch changes the default behavior of the `@overload` 

119 decorator, allowing the implementations inside the preceding 

120 @overload-decorated functions. This is opposed to the standard behavior 

121 when @overload-decorated functions can only provide type signature, 

122 while the general implementation should be contained in the function 

123 of the same name without an `@overload` decorator. 

124 

125 The trick is taken from https://beartype.github.io/plum/integration.html. 

126 

127 .. note:: 

128 For dispatching to work, the empty @dispatch-decorated function should 

129 follow (not precede) the @overload-decorated implementations in the code. 

130 """ 

131 raise NotImplementedError( 

132 "feature_map_from_kernel is not implemented for the kernel of type %s." 

133 % str(type(kernel)) 

134 ) 

135 

136 

137@overload 

138def feature_map_from_space(space: DiscreteSpectrumSpace, num: int): 

139 if isinstance(space, CompactMatrixLieGroup): 

140 return RandomPhaseFeatureMapCompact( 

141 space, num, MaternGeometricKernel._DEFAULT_NUM_RANDOM_PHASES 

142 ) 

143 elif isinstance(space, Hypersphere): 

144 num_computed_levels = space.num_computed_levels 

145 if num_computed_levels > 0: 

146 return DeterministicFeatureMapCompact(space, min(num, num_computed_levels)) 

147 else: 

148 return RandomPhaseFeatureMapCompact( 

149 space, num, MaternGeometricKernel._DEFAULT_NUM_RANDOM_PHASES 

150 ) 

151 else: 

152 

153 return DeterministicFeatureMapCompact(space, num) 

154 

155 

156@overload 

157def feature_map_from_space(space: NoncompactSymmetricSpace, num: int): 

158 if isinstance(space, Hyperbolic): 

159 return RejectionSamplingFeatureMapHyperbolic(space, num) 

160 elif isinstance(space, SymmetricPositiveDefiniteMatrices): 

161 return RejectionSamplingFeatureMapSPD(space, num) 

162 else: 

163 return RandomPhaseFeatureMapNoncompact(space, num) 

164 

165 

166@dispatch 

167def feature_map_from_space(space: Space, num: int): 

168 """ 

169 Return the default feature map for the specified space `space` and 

170 approximation level `num`. 

171 

172 :param space: 

173 A space to construct the feature map on. 

174 :param num: 

175 Approximation level. 

176 

177 :return: 

178 A feature map. 

179 :rtype: feature_maps.FeatureMap 

180 

181 .. note:: 

182 This function is organized as an abstract dispatcher plus a set of 

183 @overload-decorated implementations, one for each type of spaces. 

184 

185 When followed by an "empty" @dispatch-decorated function of the same 

186 name, plum-dispatch changes the default behavior of the `@overload` 

187 decorator, allowing the implementations inside the preceding 

188 @overload-decorated functions. This is opposed to the standard behavior 

189 when @overload-decorated functions can only provide type signature, 

190 while the general implementation should be contained in the function 

191 of the same name without an `@overload` decorator. 

192 

193 The trick is taken from https://beartype.github.io/plum/integration.html. 

194 

195 .. note:: 

196 For dispatching to work, the empty @dispatch-decorated function should 

197 follow (not precede) the @overload-decorated implementations in the code. 

198 """ 

199 raise NotImplementedError( 

200 "feature_map_from_space is not implemented for the space of type %s." 

201 % str(type(space)) 

202 ) 

203 

204 

205@overload 

206def default_num(space: DiscreteSpectrumSpace) -> int: 

207 if isinstance(space, CompactMatrixLieGroup): 

208 return MaternGeometricKernel._DEFAULT_NUM_LEVELS_LIE_GROUP 

209 elif isinstance(space, (Graph, Mesh)): 

210 return min( 

211 MaternGeometricKernel._DEFAULT_NUM_EIGENFUNCTIONS, space.num_vertices 

212 ) 

213 elif isinstance(space, GraphEdges): 

214 return min(MaternGeometricKernel._DEFAULT_NUM_EIGENFUNCTIONS, space.num_edges) 

215 elif isinstance(space, HypercubeGraph): 

216 return min(MaternGeometricKernel._DEFAULT_NUM_LEVELS, space.dim + 1) 

217 else: 

218 return MaternGeometricKernel._DEFAULT_NUM_LEVELS 

219 

220 

221@overload 

222def default_num(space: NoncompactSymmetricSpace) -> int: 

223 return MaternGeometricKernel._DEFAULT_NUM_RANDOM_PHASES 

224 

225 

226@dispatch 

227def default_num(space: Space) -> int: 

228 """ 

229 Return the default approximation level for the `space`. 

230 

231 :param space: 

232 A space. 

233 

234 :return: 

235 The default approximation level. 

236 

237 .. note:: 

238 This function is organized as an abstract dispatcher plus a set of 

239 @overload-decorated implementations, one for each type of spaces. 

240 

241 When followed by an "empty" @dispatch-decorated function of the same 

242 name, plum-dispatch changes the default behavior of the `@overload` 

243 decorator, allowing the implementations inside the preceding 

244 @overload-decorated functions. This is opposed to the standard behavior 

245 when @overload-decorated functions can only provide type signature, 

246 while the general implementation should be contained in the function 

247 of the same name without an `@overload` decorator. 

248 

249 The trick is taken from https://beartype.github.io/plum/integration.html. 

250 

251 .. note:: 

252 For dispatching to work, the empty @dispatch-decorated function should 

253 follow (not precede) the @overload-decorated implementations in the code. 

254 """ 

255 raise NotImplementedError( 

256 "default_num is not implemented for the space of type %s." % str(type(space)) 

257 ) 

258 

259 

260class MaternGeometricKernel: 

261 """ 

262 This class represents a Matérn geometric kernel that "just works". Unless 

263 you really know what you are doing, you should always use this kernel class. 

264 

265 Upon creation, unpacks into a specific geometric kernel based on the 

266 provided space, and, optionally, the associated (approximate) feature map. 

267 """ 

268 

269 _DEFAULT_NUM_EIGENFUNCTIONS = 1000 

270 _DEFAULT_NUM_LEVELS = 25 

271 _DEFAULT_NUM_LEVELS_LIE_GROUP = 20 

272 _DEFAULT_NUM_RANDOM_PHASES = 3000 

273 

274 def __new__( 

275 cls, 

276 space: Space, 

277 num: int = None, 

278 normalize: bool = True, 

279 return_feature_map: bool = False, 

280 **kwargs, 

281 ): 

282 r""" 

283 Construct a kernel and (if `return_feature_map` is `True`) a 

284 feature map on `space`. 

285 

286 .. note:: 

287 See :doc:`this page </theory/feature_maps>` for a brief 

288 introduction into feature maps. 

289 

290 :param space: 

291 Space to construct the kernel on. 

292 :param num: 

293 If provided, controls the "order of approximation" of the kernel. 

294 For the discrete spectrum spaces, this means the number of "levels" 

295 that go into the truncated series that defines the kernel (for 

296 example, these are unique eigenvalues for the 

297 :class:`~.spaces.Hypersphere` or eigenvalues with repetitions for 

298 the :class:`~.spaces.Graph` or for the :class:`~.spaces.Mesh`). 

299 For the non-compact symmetric spaces 

300 (:class:`~.spaces.NoncompactSymmetricSpace`), this is the number 

301 of random phases used to construct the kernel. 

302 

303 If num=None, we use a (hopefully) reasonable default, which is 

304 space-dependent. 

305 :param normalize: 

306 Normalize the kernel (and the feature map). If normalize=True, 

307 then either $k(x, x) = 1$ for all $x \in X$, where $X$ is the 

308 `space`, or $\int_X k(x, x) d x = 1$, depending on the space. 

309 

310 Defaults to True. 

311 

312 .. note:: 

313 For many kernel methods, $k(\cdot, \cdot)$ and 

314 $a k(\cdot, \cdot)$ are indistinguishable, whatever the 

315 positive constant $a$ is. For these, it makes sense to use 

316 normalize=False to save up some computational overhead. For 

317 others, like for the Gaussian process regression, the 

318 normalization of the kernel might be important. In 

319 these cases, you will typically want to set normalize=True. 

320 

321 :param return_feature_map: 

322 If `True`, return a feature map (needed e.g. for efficient sampling 

323 from Gaussian processes) along with the kernel. 

324 

325 Default is False. 

326 

327 :param ``**kwargs``: 

328 Any additional keyword arguments to be passed to the kernel 

329 (like `key`). 

330 

331 .. note:: 

332 For non-compact symmetric spaces, like :class:`~.spaces.Hyperbolic` 

333 or :class:`~.spaces.SymmetricPositiveDefiniteMatrices`, the `key` 

334 **must** be provided in ``**kwargs``. 

335 """ 

336 

337 kernel: BaseGeometricKernel 

338 if isinstance(space, DiscreteSpectrumSpace): 

339 num = num or default_num(space) 

340 if isinstance(space, HodgeDiscreteSpectrumSpace): 

341 kernel = MaternHodgeCompositionalKernel(space, num, normalize=normalize) 

342 else: 

343 kernel = MaternKarhunenLoeveKernel(space, num, normalize=normalize) 

344 if return_feature_map: 

345 feature_map = default_feature_map(kernel=kernel) 

346 

347 elif isinstance(space, NoncompactSymmetricSpace): 

348 num = num or default_num(space) 

349 if "key" in kwargs: 

350 key = kwargs["key"] 

351 else: 

352 raise ValueError( 

353 ( 

354 "MaternGeometricKernel for %s requires mandatory" 

355 " keyword argument 'key' which is a random number" 

356 " generator specific to the backend used" 

357 ) 

358 % str(type(space)) 

359 ) 

360 feature_map = default_feature_map(space=space, num=num) 

361 kernel = MaternFeatureMapKernel( 

362 space, feature_map, key, normalize=normalize 

363 ) 

364 else: 

365 raise NotImplementedError 

366 

367 if return_feature_map: 

368 return kernel, feature_map 

369 else: 

370 return kernel