statgpu 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. statgpu/__init__.py +174 -0
  2. statgpu/_base.py +544 -0
  3. statgpu/_config.py +127 -0
  4. statgpu/anova/__init__.py +5 -0
  5. statgpu/anova/_oneway.py +194 -0
  6. statgpu/backends/__init__.py +83 -0
  7. statgpu/backends/_array_ops.py +529 -0
  8. statgpu/backends/_base.py +184 -0
  9. statgpu/backends/_cupy.py +453 -0
  10. statgpu/backends/_factory.py +65 -0
  11. statgpu/backends/_gpu_inference_cupy.py +214 -0
  12. statgpu/backends/_gpu_inference_torch.py +422 -0
  13. statgpu/backends/_numpy.py +324 -0
  14. statgpu/backends/_torch.py +685 -0
  15. statgpu/backends/_torch_safe.py +47 -0
  16. statgpu/backends/_utils.py +423 -0
  17. statgpu/core/__init__.py +10 -0
  18. statgpu/core/formula/__init__.py +33 -0
  19. statgpu/core/formula/_design.py +99 -0
  20. statgpu/core/formula/_parser.py +191 -0
  21. statgpu/core/formula/_terms.py +70 -0
  22. statgpu/core/formula/tests/__init__.py +0 -0
  23. statgpu/core/formula/tests/test_parser.py +194 -0
  24. statgpu/covariance/__init__.py +6 -0
  25. statgpu/covariance/_empirical.py +310 -0
  26. statgpu/covariance/_shrinkage.py +248 -0
  27. statgpu/cross_validation/__init__.py +31 -0
  28. statgpu/cross_validation/_base.py +410 -0
  29. statgpu/cross_validation/_engine.py +167 -0
  30. statgpu/diagnostics/__init__.py +7 -0
  31. statgpu/diagnostics/_regression_diagnostics.py +188 -0
  32. statgpu/feature_selection/__init__.py +24 -0
  33. statgpu/feature_selection/_knockoff.py +870 -0
  34. statgpu/feature_selection/_knockoff_utils.py +1003 -0
  35. statgpu/feature_selection/_stepwise.py +300 -0
  36. statgpu/glm_core/__init__.py +81 -0
  37. statgpu/glm_core/_base.py +202 -0
  38. statgpu/glm_core/_family.py +362 -0
  39. statgpu/glm_core/_fused.py +149 -0
  40. statgpu/glm_core/_gamma.py +111 -0
  41. statgpu/glm_core/_inverse_gaussian.py +62 -0
  42. statgpu/glm_core/_irls.py +561 -0
  43. statgpu/glm_core/_logistic.py +82 -0
  44. statgpu/glm_core/_negative_binomial.py +68 -0
  45. statgpu/glm_core/_poisson.py +60 -0
  46. statgpu/glm_core/_solver_legacy.py +100 -0
  47. statgpu/glm_core/_squared.py +53 -0
  48. statgpu/glm_core/_tweedie.py +74 -0
  49. statgpu/inference/__init__.py +239 -0
  50. statgpu/inference/_distributions_backend.py +2610 -0
  51. statgpu/inference/_multiple_testing.py +391 -0
  52. statgpu/inference/_resampling.py +1400 -0
  53. statgpu/inference/_results.py +265 -0
  54. statgpu/linear_model/__init__.py +75 -0
  55. statgpu/linear_model/_gaussian_inference.py +306 -0
  56. statgpu/linear_model/_glm_base.py +1261 -0
  57. statgpu/linear_model/_ordered_logit.py +52 -0
  58. statgpu/linear_model/_ordered_probit.py +50 -0
  59. statgpu/linear_model/_stats.py +170 -0
  60. statgpu/linear_model/cv/__init__.py +13 -0
  61. statgpu/linear_model/cv/_elasticnet_cv.py +892 -0
  62. statgpu/linear_model/cv/_lasso_cv.py +253 -0
  63. statgpu/linear_model/cv/_logistic_cv.py +895 -0
  64. statgpu/linear_model/cv/_ridge_cv.py +1160 -0
  65. statgpu/linear_model/legacy/__init__.py +1 -0
  66. statgpu/linear_model/legacy/_distributions_legacy_gpu.py +340 -0
  67. statgpu/linear_model/legacy/_elasticnet_legacy.py +936 -0
  68. statgpu/linear_model/legacy/_lasso_legacy.py +4876 -0
  69. statgpu/linear_model/legacy/_penalized_legacy.py +1174 -0
  70. statgpu/linear_model/legacy/_ridge_legacy.py +863 -0
  71. statgpu/linear_model/legacy/_solver_legacy.py +104 -0
  72. statgpu/linear_model/penalized/__init__.py +25 -0
  73. statgpu/linear_model/penalized/_base.py +437 -0
  74. statgpu/linear_model/penalized/_fit_mixin.py +1877 -0
  75. statgpu/linear_model/penalized/_inference_mixin.py +1179 -0
  76. statgpu/linear_model/penalized/_penalized_cv.py +2699 -0
  77. statgpu/linear_model/penalized/_penalized_gamma.py +86 -0
  78. statgpu/linear_model/penalized/_penalized_inverse_gaussian.py +62 -0
  79. statgpu/linear_model/penalized/_penalized_linear.py +236 -0
  80. statgpu/linear_model/penalized/_penalized_logistic.py +100 -0
  81. statgpu/linear_model/penalized/_penalized_negative_binomial.py +65 -0
  82. statgpu/linear_model/penalized/_penalized_poisson.py +62 -0
  83. statgpu/linear_model/penalized/_penalized_tweedie.py +65 -0
  84. statgpu/linear_model/penalized/_predict_mixin.py +182 -0
  85. statgpu/linear_model/wrappers/__init__.py +31 -0
  86. statgpu/linear_model/wrappers/_adaptive_lasso.py +63 -0
  87. statgpu/linear_model/wrappers/_elasticnet.py +75 -0
  88. statgpu/linear_model/wrappers/_gamma.py +67 -0
  89. statgpu/linear_model/wrappers/_inverse_gaussian.py +47 -0
  90. statgpu/linear_model/wrappers/_lasso.py +2124 -0
  91. statgpu/linear_model/wrappers/_linear.py +1127 -0
  92. statgpu/linear_model/wrappers/_logistic.py +1435 -0
  93. statgpu/linear_model/wrappers/_mcp.py +58 -0
  94. statgpu/linear_model/wrappers/_negative_binomial.py +58 -0
  95. statgpu/linear_model/wrappers/_poisson.py +48 -0
  96. statgpu/linear_model/wrappers/_ridge.py +166 -0
  97. statgpu/linear_model/wrappers/_scad.py +58 -0
  98. statgpu/linear_model/wrappers/_tweedie.py +57 -0
  99. statgpu/metrics/__init__.py +21 -0
  100. statgpu/metrics/_classification.py +591 -0
  101. statgpu/nonparametric/__init__.py +50 -0
  102. statgpu/nonparametric/kernel_methods/__init__.py +25 -0
  103. statgpu/nonparametric/kernel_methods/_kernels.py +246 -0
  104. statgpu/nonparametric/kernel_methods/_krr.py +234 -0
  105. statgpu/nonparametric/kernel_methods/_krr_cv.py +380 -0
  106. statgpu/nonparametric/kernel_smoothing/__init__.py +39 -0
  107. statgpu/nonparametric/kernel_smoothing/_bandwidth_selection.py +1083 -0
  108. statgpu/nonparametric/kernel_smoothing/_kde.py +761 -0
  109. statgpu/nonparametric/kernel_smoothing/_kernel_common.py +348 -0
  110. statgpu/nonparametric/kernel_smoothing/_kernel_regression.py +748 -0
  111. statgpu/nonparametric/splines/__init__.py +5 -0
  112. statgpu/nonparametric/splines/_bspline_basis.py +336 -0
  113. statgpu/nonparametric/splines/_penalized.py +349 -0
  114. statgpu/panel/__init__.py +19 -0
  115. statgpu/panel/_covariance.py +140 -0
  116. statgpu/panel/_fixed_effects.py +420 -0
  117. statgpu/panel/_random_effects.py +385 -0
  118. statgpu/panel/_utils.py +482 -0
  119. statgpu/penalties/__init__.py +139 -0
  120. statgpu/penalties/_adaptive_l1.py +313 -0
  121. statgpu/penalties/_base.py +261 -0
  122. statgpu/penalties/_categories.py +39 -0
  123. statgpu/penalties/_elasticnet.py +98 -0
  124. statgpu/penalties/_group_lasso.py +678 -0
  125. statgpu/penalties/_group_mcp.py +553 -0
  126. statgpu/penalties/_group_scad.py +605 -0
  127. statgpu/penalties/_l1.py +107 -0
  128. statgpu/penalties/_l2.py +77 -0
  129. statgpu/penalties/_mcp.py +237 -0
  130. statgpu/penalties/_scad.py +260 -0
  131. statgpu/semiparametric/__init__.py +5 -0
  132. statgpu/semiparametric/_gam.py +401 -0
  133. statgpu/solvers/__init__.py +24 -0
  134. statgpu/solvers/_admm.py +241 -0
  135. statgpu/solvers/_constants.py +15 -0
  136. statgpu/solvers/_convergence.py +6 -0
  137. statgpu/solvers/_fista.py +436 -0
  138. statgpu/solvers/_fista_bb.py +513 -0
  139. statgpu/solvers/_fista_lla.py +541 -0
  140. statgpu/solvers/_lbfgs.py +206 -0
  141. statgpu/solvers/_newton.py +149 -0
  142. statgpu/solvers/_utils.py +277 -0
  143. statgpu/survival/__init__.py +14 -0
  144. statgpu/survival/_cox.py +3974 -0
  145. statgpu/survival/_cox_breslow_triton_kernel.py +106 -0
  146. statgpu/survival/_cox_cv.py +1159 -0
  147. statgpu/survival/_cox_efron_cuda.py +1280 -0
  148. statgpu/survival/_cox_efron_triton.py +359 -0
  149. statgpu/unsupervised/__init__.py +29 -0
  150. statgpu/unsupervised/_agglomerative.py +307 -0
  151. statgpu/unsupervised/_dbscan.py +263 -0
  152. statgpu/unsupervised/_dbscan_cpu.pyx +125 -0
  153. statgpu/unsupervised/_gmm.py +332 -0
  154. statgpu/unsupervised/_incremental_pca.py +176 -0
  155. statgpu/unsupervised/_kmeans.py +261 -0
  156. statgpu/unsupervised/_minibatch_kmeans.py +299 -0
  157. statgpu/unsupervised/_minibatch_nmf.py +252 -0
  158. statgpu/unsupervised/_nmf.py +190 -0
  159. statgpu/unsupervised/_pca.py +189 -0
  160. statgpu/unsupervised/_truncated_svd.py +132 -0
  161. statgpu/unsupervised/_tsne.py +192 -0
  162. statgpu/unsupervised/_umap.py +224 -0
  163. statgpu/unsupervised/_utils.py +134 -0
  164. statgpu-0.1.0.dist-info/METADATA +245 -0
  165. statgpu-0.1.0.dist-info/RECORD +168 -0
  166. statgpu-0.1.0.dist-info/WHEEL +5 -0
  167. statgpu-0.1.0.dist-info/licenses/LICENSE +199 -0
  168. statgpu-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,246 @@
1
+ """
2
+ Pairwise kernel functions with backend-agnostic (xp) interface.
3
+
4
+ All functions accept an ``xp`` argument that should be a NumPy-compatible
5
+ array module (numpy, cupy, or torch). When *xp* is ``None`` the functions
6
+ fall back to ``numpy``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Optional
12
+
13
+ import numpy as np
14
+
15
+ from statgpu.backends import xp_maximum
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Individual kernel functions
20
+ # ---------------------------------------------------------------------------
21
+
22
+ def rbf_kernel(X, Y=None, gamma=None, xp=None):
23
+ r"""Radial basis function (Gaussian) kernel.
24
+
25
+ .. math::
26
+ K(x, y) = \exp(-\gamma \|x - y\|^2)
27
+
28
+ Parameters
29
+ ----------
30
+ X : array-like of shape (n_samples_X, n_features)
31
+ Y : array-like of shape (n_samples_Y, n_features), optional
32
+ gamma : float, optional
33
+ Kernel coefficient. Defaults to ``1 / n_features``.
34
+ xp : module, optional
35
+ Array module (numpy / cupy / torch).
36
+
37
+ Returns
38
+ -------
39
+ K : array of shape (n_samples_X, n_samples_Y)
40
+ """
41
+ if xp is None:
42
+ xp = np
43
+ if Y is None:
44
+ Y = X
45
+ if gamma is None:
46
+ gamma = 1.0 / X.shape[1]
47
+
48
+ # ||x - y||^2 = ||x||^2 + ||y||^2 - 2 * x @ y.T
49
+ XX = xp.sum(X ** 2, axis=1)[:, None]
50
+ YY = xp.sum(Y ** 2, axis=1)[None, :]
51
+ dist = XX + YY - 2.0 * (X @ Y.T)
52
+ # Clamp to avoid negative values from numerical noise
53
+ dist = xp_maximum(dist, 0.0, xp)
54
+ return xp.exp(-gamma * dist)
55
+
56
+
57
+ def polynomial_kernel(X, Y=None, degree=3, gamma=None, coef0=1, xp=None):
58
+ r"""Polynomial kernel.
59
+
60
+ .. math::
61
+ K(x, y) = (\gamma \, x^\top y + c_0)^d
62
+
63
+ Parameters
64
+ ----------
65
+ X : array-like of shape (n_samples_X, n_features)
66
+ Y : array-like of shape (n_samples_Y, n_features), optional
67
+ degree : int, default=3
68
+ gamma : float, optional
69
+ Defaults to ``1 / n_features``.
70
+ coef0 : float, default=1
71
+ xp : module, optional
72
+
73
+ Returns
74
+ -------
75
+ K : array of shape (n_samples_X, n_samples_Y)
76
+ """
77
+ if xp is None:
78
+ xp = np
79
+ if Y is None:
80
+ Y = X
81
+ if gamma is None:
82
+ gamma = 1.0 / X.shape[1]
83
+
84
+ return (gamma * (X @ Y.T) + coef0) ** degree
85
+
86
+
87
+ def linear_kernel(X, Y=None, xp=None):
88
+ r"""Linear kernel.
89
+
90
+ .. math::
91
+ K(x, y) = x^\top y
92
+
93
+ Parameters
94
+ ----------
95
+ X : array-like of shape (n_samples_X, n_features)
96
+ Y : array-like of shape (n_samples_Y, n_features), optional
97
+ xp : module, optional
98
+
99
+ Returns
100
+ -------
101
+ K : array of shape (n_samples_X, n_samples_Y)
102
+ """
103
+ if xp is None:
104
+ xp = np
105
+ if Y is None:
106
+ Y = X
107
+ return X @ Y.T
108
+
109
+
110
+ def laplacian_kernel(X, Y=None, gamma=None, xp=None):
111
+ r"""Laplacian kernel.
112
+
113
+ .. math::
114
+ K(x, y) = \exp(-\gamma \|x - y\|_1)
115
+
116
+ Parameters
117
+ ----------
118
+ X : array-like of shape (n_samples_X, n_features)
119
+ Y : array-like of shape (n_samples_Y, n_features), optional
120
+ gamma : float, optional
121
+ Defaults to ``1 / n_features``.
122
+ xp : module, optional
123
+
124
+ Returns
125
+ -------
126
+ K : array of shape (n_samples_X, n_samples_Y)
127
+ """
128
+ if xp is None:
129
+ xp = np
130
+ if Y is None:
131
+ Y = X
132
+ if gamma is None:
133
+ gamma = 1.0 / X.shape[1]
134
+
135
+ # L1 distance using broadcasting
136
+ dist = xp.sum(xp.abs(X[:, None, :] - Y[None, :, :]), axis=2)
137
+ return xp.exp(-gamma * dist)
138
+
139
+
140
+ def sigmoid_kernel(X, Y=None, gamma=None, coef0=1, xp=None):
141
+ r"""Sigmoid (hyperbolic tangent) kernel.
142
+
143
+ .. math::
144
+ K(x, y) = \tanh(\gamma \, x^\top y + c_0)
145
+
146
+ Parameters
147
+ ----------
148
+ X : array-like of shape (n_samples_X, n_features)
149
+ Y : array-like of shape (n_samples_Y, n_features), optional
150
+ gamma : float, optional
151
+ Defaults to ``1 / n_features``.
152
+ coef0 : float, default=1
153
+ xp : module, optional
154
+
155
+ Returns
156
+ -------
157
+ K : array of shape (n_samples_X, n_samples_Y)
158
+ """
159
+ if xp is None:
160
+ xp = np
161
+ if Y is None:
162
+ Y = X
163
+ if gamma is None:
164
+ gamma = 1.0 / X.shape[1]
165
+
166
+ return xp.tanh(gamma * (X @ Y.T) + coef0)
167
+
168
+
169
+ def cosine_kernel(X, Y=None, xp=None):
170
+ r"""Cosine similarity kernel.
171
+
172
+ .. math::
173
+ K(x, y) = \frac{x^\top y}{\|x\| \, \|y\|}
174
+
175
+ Parameters
176
+ ----------
177
+ X : array-like of shape (n_samples_X, n_features)
178
+ Y : array-like of shape (n_samples_Y, n_features), optional
179
+ xp : module, optional
180
+
181
+ Returns
182
+ -------
183
+ K : array of shape (n_samples_X, n_samples_Y)
184
+ """
185
+ if xp is None:
186
+ xp = np
187
+ if Y is None:
188
+ Y = X
189
+
190
+ X_norm = xp.sqrt(xp.sum(X ** 2, axis=1))[:, None]
191
+ Y_norm = xp.sqrt(xp.sum(Y ** 2, axis=1))[None, :]
192
+ return (X @ Y.T) / (X_norm * Y_norm + 1e-10)
193
+
194
+
195
+ # ---------------------------------------------------------------------------
196
+ # Registry
197
+ # ---------------------------------------------------------------------------
198
+
199
+ KERNEL_REGISTRY = {
200
+ "rbf": rbf_kernel,
201
+ "gaussian": rbf_kernel,
202
+ "linear": linear_kernel,
203
+ "polynomial": polynomial_kernel,
204
+ "poly": polynomial_kernel,
205
+ "laplacian": laplacian_kernel,
206
+ "sigmoid": sigmoid_kernel,
207
+ "cosine": cosine_kernel,
208
+ }
209
+
210
+
211
+ def pairwise_kernels(X, Y=None, metric="rbf", xp=None, **params):
212
+ """Compute the kernel between arrays X and Y using the given metric.
213
+
214
+ Parameters
215
+ ----------
216
+ X : array-like of shape (n_samples_X, n_features)
217
+ Y : array-like of shape (n_samples_Y, n_features), optional
218
+ metric : str or callable, default='rbf'
219
+ Kernel metric name or a callable.
220
+ xp : module, optional
221
+ Array module.
222
+ **params
223
+ Additional keyword arguments forwarded to the kernel function.
224
+
225
+ Returns
226
+ -------
227
+ K : array of shape (n_samples_X, n_samples_Y)
228
+ """
229
+ if callable(metric):
230
+ # Try calling with xp parameter first; fall back without it
231
+ # for user-defined callables that don't accept xp.
232
+ # Pass Y as-is (including None) so callables can distinguish
233
+ # self-kernel (Y=None) from cross-kernel.
234
+ try:
235
+ return metric(X, Y, xp=xp, **params)
236
+ except TypeError:
237
+ return metric(X, Y, **params)
238
+
239
+ key = str(metric).strip().lower()
240
+ func = KERNEL_REGISTRY.get(key)
241
+ if func is None:
242
+ raise ValueError(
243
+ f"Unknown kernel metric '{metric}'. "
244
+ f"Available: {sorted(KERNEL_REGISTRY.keys())}"
245
+ )
246
+ return func(X, Y, xp=xp, **params)
@@ -0,0 +1,234 @@
1
+ """
2
+ Kernel Ridge Regression with GPU acceleration.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Optional, Union
8
+
9
+ import numpy as np
10
+
11
+ from statgpu._base import BaseEstimator
12
+ from statgpu._config import Device
13
+ from statgpu.backends import _LINALG_ERRORS, _to_numpy, _torch_dev, xp_eye, xp_astype
14
+
15
+ from statgpu.nonparametric.kernel_methods._kernels import pairwise_kernels
16
+
17
+
18
+ class KernelRidge(BaseEstimator):
19
+ r"""Kernel Ridge Regression.
20
+
21
+ Solves the dual problem:
22
+
23
+ .. math::
24
+ \boldsymbol{\alpha} = (K + \alpha I)^{-1} \mathbf{y}
25
+
26
+ where :math:`K` is the kernel matrix of the training data. Predictions
27
+ are computed as :math:`\hat{y} = K_{\text{test}} \boldsymbol{\alpha}`.
28
+
29
+ Parameters
30
+ ----------
31
+ alpha : float, default=1.0
32
+ Regularization strength.
33
+ kernel : str or callable, default='rbf'
34
+ Kernel metric name (``'rbf'``, ``'linear'``, ``'polynomial'``,
35
+ ``'laplacian'``, ``'sigmoid'``, ``'cosine'``) or a callable.
36
+ gamma : float, optional
37
+ Kernel coefficient for rbf, polynomial, laplacian, sigmoid.
38
+ Defaults to ``1 / n_features``.
39
+ degree : int, default=3
40
+ Degree for the polynomial kernel.
41
+ coef0 : float, default=1
42
+ Independent term for polynomial and sigmoid kernels.
43
+ kernel_params : dict, optional
44
+ Additional parameters passed to the kernel function.
45
+ device : str or Device, default='auto'
46
+ Computation device.
47
+ n_jobs : int, optional
48
+ Not used; kept for API compatibility.
49
+
50
+ Attributes
51
+ ----------
52
+ dual_coef_ : ndarray of shape (n_samples,) or (n_samples, n_targets)
53
+ Dual coefficients in the kernel space.
54
+ X_fit_ : ndarray of shape (n_samples, n_features)
55
+ Training data stored for prediction.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ alpha: float = 1.0,
61
+ kernel: Union[str, callable] = "rbf",
62
+ gamma: Optional[float] = None,
63
+ degree: int = 3,
64
+ coef0: float = 1,
65
+ kernel_params: Optional[dict] = None,
66
+ device: Union[str, Device] = Device.AUTO,
67
+ n_jobs: Optional[int] = None,
68
+ ):
69
+ super().__init__(device=device, n_jobs=n_jobs)
70
+ self.alpha = alpha
71
+ self.kernel = kernel
72
+ self.gamma = gamma
73
+ self.degree = degree
74
+ self.coef0 = coef0
75
+ self.kernel_params = kernel_params
76
+
77
+ # Fitted attributes
78
+ self.dual_coef_ = None
79
+ self.X_fit_ = None
80
+ self._xp = None
81
+ self._backend = None
82
+
83
+ def _get_kernel_params(self):
84
+ """Collect kernel-specific parameters."""
85
+ params = {}
86
+ if self.kernel_params is not None:
87
+ params.update(self.kernel_params)
88
+ # Only pass gamma/degree/coef0 for kernels that use them
89
+ k = str(self.kernel).strip().lower() if isinstance(self.kernel, str) else ""
90
+ if k in ("rbf", "gaussian", "polynomial", "poly", "laplacian", "sigmoid"):
91
+ if self.gamma is not None:
92
+ params["gamma"] = self.gamma
93
+ if k in ("polynomial", "poly", "sigmoid"):
94
+ if self.degree != 3 and k in ("polynomial", "poly"):
95
+ params["degree"] = self.degree
96
+ if self.coef0 != 1:
97
+ params["coef0"] = self.coef0
98
+ return params
99
+
100
+ def fit(self, X, y, sample_weight=None):
101
+ """Fit Kernel Ridge Regression model.
102
+
103
+ Parameters
104
+ ----------
105
+ X : array-like of shape (n_samples, n_features)
106
+ Training data.
107
+ y : array-like of shape (n_samples,) or (n_samples, n_targets)
108
+ Target values.
109
+ sample_weight : ignored
110
+ Not used; kept for API compatibility.
111
+
112
+ Returns
113
+ -------
114
+ self
115
+ """
116
+ # Resolve backend and convert arrays
117
+ self._backend = self._get_backend()
118
+ xp = self._backend.xp
119
+ self._xp = xp
120
+
121
+ X_arr = self._to_array(X)
122
+ y_arr = xp_astype(self._to_array(y), xp.float64, xp)
123
+ if y_arr.ndim == 1:
124
+ y_arr = y_arr.reshape(-1, 1)
125
+
126
+ n_samples = X_arr.shape[0]
127
+ alpha = float(self.alpha)
128
+
129
+ # Compute kernel matrix
130
+ kernel_params = self._get_kernel_params()
131
+ K = pairwise_kernels(X_arr, X_arr, metric=self.kernel, xp=xp, **kernel_params)
132
+
133
+ # Regularize: K + alpha * I
134
+ K_reg = K + alpha * xp_eye(n_samples, K.dtype, xp, K)
135
+
136
+ # Solve (K + alpha I) * dual_coef = y with jitter fallback
137
+ try:
138
+ self.dual_coef_ = xp.linalg.solve(K_reg, y_arr)
139
+ except _LINALG_ERRORS:
140
+ # Matrix may be ill-conditioned; add jitter and retry
141
+ jitter = float(xp.max(xp.abs(xp.diag(K)))) * 1e-10
142
+ for _ in range(6):
143
+ K_reg = K_reg + jitter * xp_eye(n_samples, K.dtype, xp, K)
144
+ try:
145
+ self.dual_coef_ = xp.linalg.solve(K_reg, y_arr)
146
+ break
147
+ except _LINALG_ERRORS:
148
+ jitter *= 10
149
+ else:
150
+ raise ValueError(
151
+ "KernelRidge: regularized kernel matrix is singular "
152
+ "even after jitter escalation. Try increasing alpha."
153
+ )
154
+
155
+ self.X_fit_ = X_arr
156
+ self._fitted = True
157
+ return self
158
+
159
+ def predict(self, X):
160
+ """Predict using the kernel ridge model.
161
+
162
+ Parameters
163
+ ----------
164
+ X : array-like of shape (n_samples_test, n_features)
165
+
166
+ Returns
167
+ -------
168
+ y_pred : ndarray of shape (n_samples_test,) or (n_samples_test, n_targets)
169
+ """
170
+ self._check_is_fitted()
171
+ xp = self._xp
172
+
173
+ X_arr = self._to_array(X)
174
+ kernel_params = self._get_kernel_params()
175
+ K_test = pairwise_kernels(X_arr, self.X_fit_, metric=self.kernel, xp=xp, **kernel_params)
176
+
177
+ y_pred = K_test @ self.dual_coef_
178
+ # Flatten if single target
179
+ if y_pred.ndim == 2 and y_pred.shape[1] == 1:
180
+ y_pred = y_pred.ravel()
181
+ return y_pred
182
+
183
+ def score(self, X, y):
184
+ """Return the coefficient of determination R^2.
185
+
186
+ Parameters
187
+ ----------
188
+ X : array-like of shape (n_samples, n_features)
189
+ y : array-like of shape (n_samples,) or (n_samples, n_targets)
190
+
191
+ Returns
192
+ -------
193
+ score : float
194
+ R^2 score.
195
+ """
196
+ self._check_is_fitted()
197
+ xp = self._xp
198
+
199
+ y_pred = self.predict(X)
200
+ y_arr = xp_astype(self._to_array(y), xp.float64, xp)
201
+ # Ensure 1D to avoid broadcasting issues
202
+ y_arr = y_arr.ravel()
203
+ y_pred = y_pred.ravel()
204
+
205
+ ss_res = xp.sum((y_arr - y_pred) ** 2)
206
+ ss_tot = xp.sum((y_arr - xp.mean(y_arr)) ** 2)
207
+
208
+ ss_res_val = float(ss_res.item()) if hasattr(ss_res, "item") else float(ss_res)
209
+ ss_tot_val = float(ss_tot.item()) if hasattr(ss_tot, "item") else float(ss_tot)
210
+
211
+ if ss_tot_val == 0.0:
212
+ return 0.0
213
+ return 1.0 - ss_res_val / ss_tot_val
214
+
215
+ def get_params(self, deep=True):
216
+ """Get parameters for this estimator."""
217
+ params = super().get_params(deep=deep)
218
+ params.update({
219
+ "alpha": self.alpha,
220
+ "kernel": self.kernel,
221
+ "gamma": self.gamma,
222
+ "degree": self.degree,
223
+ "coef0": self.coef0,
224
+ "kernel_params": self.kernel_params,
225
+ })
226
+ return params
227
+
228
+ def set_params(self, **params):
229
+ """Set parameters for this estimator."""
230
+ super().set_params(**params)
231
+ for key in ("alpha", "kernel", "gamma", "degree", "coef0", "kernel_params"):
232
+ if key in params:
233
+ setattr(self, key, params[key])
234
+ return self