From 20d12ab32e3eee7c44bb0a6fa69919eacff8cda3 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 00:34:06 +0300 Subject: [PATCH 01/14] Rewrite asserts with appropriate exceptions --- .../feature_maps/deterministic.py | 13 +- .../feature_maps/probability_densities.py | 56 +++--- geometric_kernels/kernels/feature_map.py | 21 ++- .../kernels/hodge_compositional.py | 21 +-- geometric_kernels/kernels/karhunen_loeve.py | 56 ++++-- geometric_kernels/kernels/product.py | 19 +- geometric_kernels/spaces/circle.py | 3 +- geometric_kernels/spaces/graph.py | 19 +- geometric_kernels/spaces/graph_edges.py | 162 ++++++++---------- geometric_kernels/spaces/hypercube_graph.py | 3 +- geometric_kernels/spaces/mesh.py | 5 +- geometric_kernels/spaces/product.py | 25 +-- .../utils/kernel_formulas/euclidean.py | 12 +- .../utils/kernel_formulas/hypercube_graph.py | 10 +- .../utils/kernel_formulas/spd.py | 9 +- geometric_kernels/utils/manifold_utils.py | 9 +- geometric_kernels/utils/product.py | 10 +- geometric_kernels/utils/special_functions.py | 15 +- geometric_kernels/utils/utils.py | 36 +++- 19 files changed, 293 insertions(+), 211 deletions(-) diff --git a/geometric_kernels/feature_maps/deterministic.py b/geometric_kernels/feature_maps/deterministic.py index 39875d79..56714e56 100644 --- a/geometric_kernels/feature_maps/deterministic.py +++ b/geometric_kernels/feature_maps/deterministic.py @@ -43,15 +43,20 @@ def __init__( self.num_levels = num_levels if repeated_eigenvalues_laplacian is None: - assert eigenfunctions is None + if eigenfunctions is not None: + raise ValueError("If you provide `eigenfunctions`, you must also provide the corresponding `repeated_eigenvalues_laplacian`.") repeated_eigenvalues_laplacian = self.space.get_repeated_eigenvalues( self.num_levels ) eigenfunctions = self.space.get_eigenfunctions(self.num_levels) else: - assert eigenfunctions is not None - assert repeated_eigenvalues_laplacian.shape == (num_levels, 1) - assert eigenfunctions.num_levels == num_levels + if eigenfunctions is None: + raise ValueError("If you provide `repeated_eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`.") + if repeated_eigenvalues_laplacian.shape != (num_levels, 1): + raise ValueError(f"Expected `repeated_eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {repeated_eigenvalues_laplacian.shape}") + if eigenfunctions.num_levels != num_levels: + raise ValueError(f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," + f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}") self._repeated_eigenvalues = repeated_eigenvalues_laplacian self._eigenfunctions = eigenfunctions diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index 1e472ccc..330789f3 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -22,8 +22,7 @@ eigvalsh, from_numpy, ) -from geometric_kernels.utils.utils import ordered_pairwise_differences - +from geometric_kernels.utils.utils import ordered_pairwise_differences, _check_field_in_params, _check_1_vector, _check_1_dim_vector, _check_matrix def student_t_sample( key: B.RandomState, @@ -74,13 +73,11 @@ def student_t_sample( samples of type `dtype`, and `key` is the updated random key for `jax`, or the similar random state (generator) for any other backend. """ - assert B.shape(df) == (1,), "df must be a 1-vector." - - n = int(B.length(loc)) - - assert B.shape(loc) == (n,), "loc must be a 1-dim vector" - assert B.shape(shape) == (n, n), "shape must be a matrix" + _check_1_vector(df, "df") + _check_1_dim_vector(loc, "loc") + _check_matrix(shape, "shape") + shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) key, z = B.randn(key, dtype, *size, n) @@ -140,11 +137,12 @@ def base_density_sample( of samples, and `key` is the updated random key for `jax`, or the similar random state (generator) for any other backend. """ - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") + nu = params["nu"] L = params["lengthscale"] @@ -232,7 +230,8 @@ def _alphas(n: int) -> B.Numeric: .. todo:: Update proposition numbers when the paper gets published. """ - assert n >= 2 + if n < 2: + raise ValueError("Dimension of the hyperbolic space `n` must be >= 2.") x, j = symbols("x, j") if (n % 2) == 0: m = n // 2 @@ -269,9 +268,10 @@ def _sample_mixture_heat( .. todo:: Update proposition numbers when the paper gets published. """ - assert B.rank(alpha) == 1 + _check_1_dim_vector(alpha, "alpha") m = B.shape(alpha)[0] - 1 - assert m >= 0 + if m < 0: + raise ValueError("The mixture must contain at least 1 component.") dtype = B.dtype(lengthscale) js = B.range(dtype, 0, m + 1) @@ -332,9 +332,10 @@ def _sample_mixture_matern( .. todo:: Update proposition numbers when the paper gets published. """ - assert B.rank(alpha) == 1 - m = B.shape(alpha)[0] - 1 - assert m >= 0 + _check_1_dim(alpha, "alpha") + m = B.shape(alpha)[0] - 1 + if m < 0: + raise ValueError("The mixture must contain at least 1 component.") dtype = B.dtype(lengthscale) js = B.range(dtype, 0, m + 1) if shifted_laplacian: @@ -397,10 +398,11 @@ def hyperbolic_density_sample( samples, and `key` is the updated random key for `jax`, or the similar random state (generator) for any other backend. """ - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") nu = params["nu"] L = params["lengthscale"] @@ -477,11 +479,12 @@ def spd_density_sample( samples, and `key` is the updated random key for `jax`, or the similar random state (generator) for any other backend. """ - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + nu = params["nu"] L = params["lengthscale"] @@ -514,7 +517,6 @@ def spd_density_sample( diffp = B.pi * B.abs(diffp) logprod = B.sum(B.log(B.tanh(diffp)), axis=-1) prod = B.exp(logprod) - assert B.all(prod > 0) # accept with probability `prod` key, u = B.rand(key, B.dtype(L), 1) diff --git a/geometric_kernels/kernels/feature_map.py b/geometric_kernels/kernels/feature_map.py index b224be43..926a9bdd 100644 --- a/geometric_kernels/kernels/feature_map.py +++ b/geometric_kernels/kernels/feature_map.py @@ -11,7 +11,7 @@ from geometric_kernels.feature_maps import FeatureMap from geometric_kernels.kernels.base import BaseGeometricKernel from geometric_kernels.spaces.base import Space -from geometric_kernels.utils.utils import make_deterministic +from geometric_kernels.utils.utils import make_deterministic, _check_field_in_params, _check_1_vector class MaternFeatureMapKernel(BaseGeometricKernel): @@ -108,10 +108,11 @@ def K( X2: Optional[B.Numeric] = None, **kwargs, ): - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") _, features_X = self.feature_map( X, params, normalize=self.normalize, **kwargs @@ -127,10 +128,12 @@ def K( return feature_product def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs): - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") + _, features_X = self.feature_map( X, params, normalize=self.normalize, **kwargs diff --git a/geometric_kernels/kernels/hodge_compositional.py b/geometric_kernels/kernels/hodge_compositional.py index 1180c67d..8c5ad608 100644 --- a/geometric_kernels/kernels/hodge_compositional.py +++ b/geometric_kernels/kernels/hodge_compositional.py @@ -11,6 +11,7 @@ from geometric_kernels.kernels.base import BaseGeometricKernel from geometric_kernels.kernels.karhunen_loeve import MaternKarhunenLoeveKernel from geometric_kernels.spaces import HodgeDiscreteSpectrumSpace +from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector class MaternHodgeCompositionalKernel(BaseGeometricKernel): @@ -129,13 +130,9 @@ def K( inputs, or batches of matrices of inputs, depending on the space. """ - assert all( - key in params for key in ["harmonic", "gradient", "curl"] - ), "MaternHodgeCompositionalKernel's parameters must contain keys 'harmonic', 'gradient', 'curl'." - assert all( - B.shape(params[key]["logit"]) == (1,) - for key in ["harmonic", "gradient", "curl"] - ), "The 'logit' parameters of MaternHodgeCompositionalKernel must have shape (1,)." + for key in ("harmonic", "gradient", "curl"): + _check_field_in_params(params, key) + _check_1_vector(params[key]["logit"], f"params[\"{key}\"][\"logit\"]") # Copy the parameters to avoid modifying the original dict. params = {key: params[key].copy() for key in ["harmonic", "gradient", "curl"]} @@ -162,13 +159,9 @@ def K_diag( diagonal. """ - assert all( - key in params for key in ["harmonic", "gradient", "curl"] - ), "MaternHodgeCompositionalKernel's parameters must contain keys 'harmonic', 'gradient', 'curl'." - assert all( - B.shape(params[key]["logit"]) == (1,) - for key in ["harmonic", "gradient", "curl"] - ), "The 'logit' parameters of MaternHodgeCompositionalKernel must have shape (1,)." + for key in ("harmonic", "gradient", "curl"): + _check_field_in_params(params, key) + _check_1_vector(params[key]["logit"], f"params[\"{key}\"][\"logit\"]") # Copy the parameters to avoid modifying the original dict. params = {key: params[key].copy() for key in ["harmonic", "gradient", "curl"]} diff --git a/geometric_kernels/kernels/karhunen_loeve.py b/geometric_kernels/kernels/karhunen_loeve.py index 48f38e96..dabf66e8 100644 --- a/geometric_kernels/kernels/karhunen_loeve.py +++ b/geometric_kernels/kernels/karhunen_loeve.py @@ -11,6 +11,7 @@ from geometric_kernels.lab_extras import from_numpy, is_complex from geometric_kernels.spaces import DiscreteSpectrumSpace from geometric_kernels.spaces.eigenfunctions import Eigenfunctions +from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector class MaternKarhunenLoeveKernel(BaseGeometricKernel): @@ -73,13 +74,18 @@ def __init__( self.num_levels = num_levels # in code referred to as `L`. if eigenvalues_laplacian is None: - assert eigenfunctions is None + if eigenfunctions is not None: + raise ValueError("If you provide `eigenfunctions`, you must also provide the corresponding `eigenvalues_laplacian`.") eigenvalues_laplacian = self.space.get_eigenvalues(self.num_levels) eigenfunctions = self.space.get_eigenfunctions(self.num_levels) else: - assert eigenfunctions is not None - assert eigenvalues_laplacian.shape == (num_levels, 1) - assert eigenfunctions.num_levels == num_levels + if eigenfunctions is None: + raise ValueError("If you provide `eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`.") + if eigenvalues_laplacian.shape != (num_levels, 1): + raise ValueError(f"Expected `eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {eigenvalues_laplacian.shape}") + if eigenfunctions.num_levels != num_levels: + raise ValueError(f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," + f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}") self._eigenvalues_laplacian = eigenvalues_laplacian self._eigenfunctions = eigenfunctions @@ -134,8 +140,8 @@ def spectrum( :return: The spectrum of the Matérn kernel. """ - assert lengthscale.shape == (1,) - assert nu.shape == (1,) + _check_1_vector(lenghtscale, "lengthscale") + _check_1_vector(nu, "nu") # Note: 1.0 in safe_nu can be replaced by any finite positive value safe_nu = B.where(nu == np.inf, B.cast(B.dtype(lengthscale), np.r_[1.0]), nu) @@ -180,11 +186,12 @@ def eigenvalues(self, params: Dict[str, B.Numeric]) -> B.Numeric: :return: An [L, 1]-shaped array. """ - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], "params[\"nu\"]") + spectral_values = self.spectrum( self.eigenvalues_laplacian, nu=params["nu"], @@ -210,10 +217,16 @@ def eigenvalues(self, params: Dict[str, B.Numeric]) -> B.Numeric: def K( self, params: Dict[str, B.Numeric], X: B.Numeric, X2: Optional[B.Numeric] = None, **kwargs # type: ignore ) -> B.Numeric: - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) + if "lengthscale" not in params: + raise ValueError("`params` must contain `lengthscale`.") + if params["lengthscale"].shape != (1,): + raise ValueError(f"`params['lengthscale']` must be a 1-vector.") + + if "nu" not in params: + raise ValueError("`params` must contain `nu`.") + if params["nu"].shape != (1,): + raise ValueError(f"`params['nu']` must be a 1-vector.") + weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions @@ -224,11 +237,16 @@ def K( return K def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs) -> B.Numeric: - assert "lengthscale" in params - assert params["lengthscale"].shape == (1,) - assert "nu" in params - assert params["nu"].shape == (1,) - + if "lengthscale" not in params: + raise ValueError("`params` must contain `lengthscale`.") + if params["lengthscale"].shape != (1,): + raise ValueError(f"`params['lengthscale']` must be a 1-vector.") + + if "nu" not in params: + raise ValueError("`params` must contain `nu`.") + if params["nu"].shape != (1,): + raise ValueError(f"`params['nu']` must be a 1-vector.") + weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions K_diag = Phi.weighted_outerproduct_diag(weights, X, **kwargs) # [N,] diff --git a/geometric_kernels/kernels/product.py b/geometric_kernels/kernels/product.py index 69dcdde6..60d786c7 100644 --- a/geometric_kernels/kernels/product.py +++ b/geometric_kernels/kernels/product.py @@ -63,7 +63,8 @@ def __init__( self.spaces: List[Space] = [] for kernel in self.kernels: # Make sure there is no product kernel in the list of kernels. - assert isinstance(kernel.space, Space) + if isinstance(kernel, ProductGeometricKernel): + raise NotImplementedError("One of the provided kernels is a product kernel itself.") self.spaces.append(kernel.space) self.element_shapes = [space.element_shape for space in self.spaces] self.element_dtypes = [space.element_dtype for space in self.spaces] @@ -77,9 +78,12 @@ def __init__( self.dimension_indices.append(inds[i : i + dim]) i += dim else: - assert len(dimension_indices) == len(self.kernels) + if len(dimension_indices) != len(self.kernels): + raise ValueError(f"`dimension_indices` must correspond to `kernels`, but got {len(kernels)} kernels and {len(dimension_indices)} dimension indices.") for idx_list in dimension_indices: - assert all(idx >= 0 for idx in idx_list) + for idx in idx_list: + if idx < 0: + raise ValueError(f"Expected all `dimension_indices` to be non-negative.") self.dimension_indices = dimension_indices @@ -99,10 +103,13 @@ def init_params(self) -> Dict[str, B.NPNumeric]: nu_list: List[B.NPNumeric] = [] lengthscale_list: List[B.NPNumeric] = [] - for kernel in self.kernels: + for kernel_idx, kernel in enumerate(self.kernels): cur_params = kernel.init_params() - assert cur_params["lengthscale"].shape == (1,) - assert cur_params["nu"].shape == (1,) + if cur_params["lengthscale"].shape != (1,): + raise ValueError(f"All kernels' `lengthscale`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this.") + if cur_params["nu"].shape != (1,): + raise ValueError(f"All kernels' `nu`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this.") + nu_list.append(cur_params["nu"]) lengthscale_list.append(cur_params["lengthscale"]) diff --git a/geometric_kernels/spaces/circle.py b/geometric_kernels/spaces/circle.py index d80d4d51..40f1b3b0 100644 --- a/geometric_kernels/spaces/circle.py +++ b/geometric_kernels/spaces/circle.py @@ -28,7 +28,8 @@ class SinCosEigenfunctions(EigenfunctionsWithAdditionTheorem): """ def __init__(self, num_levels: int): - assert num_levels >= 1 + if num_levels < 1: + raise ValueError("`num_levels` must be a positive integer.") self._num_eigenfunctions = num_levels * 2 - 1 self._num_levels = num_levels diff --git a/geometric_kernels/spaces/graph.py b/geometric_kernels/spaces/graph.py index bfdf37f5..3300e528 100644 --- a/geometric_kernels/spaces/graph.py +++ b/geometric_kernels/spaces/graph.py @@ -18,6 +18,7 @@ Eigenfunctions, EigenfunctionsFromEigenvectors, ) +from geometric_kernels.utils.utils import _check_matrix class Graph(DiscreteSpectrumSpace): @@ -66,15 +67,16 @@ def __str__(self): return f"Graph({self.num_vertices}, {'normalized' if self._normalized else 'unnormalized'})" @staticmethod - def _checks(adjacency): + def _checks(adjacency_matrix): """ - Checks if `adjacency` is a square symmetric matrix. + Checks if `adjacency_matrix` is a square symmetric matrix. """ - assert ( - len(adjacency.shape) == 2 and adjacency.shape[0] == adjacency.shape[1] - ), "Matrix is not square." + _check_matrix(adjacency_matrix, "adjacency_matrix") + if B.shape(adjacency_matrix)[0] != B.shape(adjacency_matrix)[1]: + raise ValueError("`adjacency_matrix` must be a square matrix.") - assert not B.any(adjacency != B.T(adjacency)), "Adjacency is not symmetric" + if B.any(adjacency_matrix != B.T(adjacency_matrix)): + raise ValueError("`adjacency_matrix` must be a symmetric matrix.") @property def dimension(self) -> int: @@ -118,9 +120,8 @@ def get_eigensystem(self, num): :return: A tuple of eigenvectors [n, num], eigenvalues [num, 1]. """ - assert ( - num <= self.num_vertices - ), "Number of eigenpairs cannot exceed the number of vertices" + if num > self.num_vertices: + raise ValueError("Number of eigenpairs cannot exceed the number of vertices.") if num not in self.cache: evals, evecs = eigenpairs(self._laplacian, num) diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index 5df4c39d..dccd93ec 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -22,6 +22,7 @@ Eigenfunctions, EigenfunctionsFromEigenvectors, ) +from geometric_kernels.utils.utils import _check_matrix, _check_1_dim_vector class GraphEdges(HodgeDiscreteSpectrumSpace): @@ -230,41 +231,32 @@ def _checks_oriented_edges( :param comprehensive: If True, perform more extensive checks. """ + _check_matrix(oriented_edges, "oriented_edges") - assert ( - B.rank(oriented_edges) == 2 - ), "The oriented_edges array must be 2-dimensional." + if B.shape(oriented_edges)[1] != 2: + raise ValueError("`oriented_edges` must have shape (*, 2).") - assert B.shape(oriented_edges)[1] == 2, "oriented_edges must have shape (*, 2)." - - assert B.dtype(oriented_edges) == int_like( - oriented_edges - ), "The oriented_edges must be an array of integers." - assert B.all( - oriented_edges >= 0 - ), "The oriented_edges array must contain only non-negative values." - assert B.all( - oriented_edges < self.num_nodes - ), "The values in the oriented_edges array must be < self.num_nodes." - assert B.all( - oriented_edges[:, 0] - oriented_edges[:, 1] != 0 - ), "Loops are not allowed." + if B.dtype(oriented_edges) != int_like(oriented_edges): + raise ValueError("`oriented_edges` must be an array of integers.") + if B.any(oriented_edges < 0): + raise ValueError("`oriented_edges` must contain only non-negative values.") + if B.any(oriented_edges >= self.num_nodes): + raise ValueError("The values in the `oriented_edges` array must be less than `self.num_nodes.`") + if B.any(oriented_edges[:, 0] - oriented_edges[:, 1] == 0): + raise ValueError("Loops are not allowed.") if comprehensive: num_edges = oriented_edges.shape[0] for i in range(num_edges): for j in range(i + 1, num_edges): - assert B.any( - oriented_edges[i, :] != oriented_edges[j, :] - ), "The oriented_edges array must not contain duplicate edges." - assert B.any( - oriented_edges[i, :] != oriented_edges[j, ::-1] - ), "The oriented_edges array must not contain duplicate edges." + if B.all(oriented_edges[i, :] == oriented_edges[j, :]): + raise ValueError("`oriented_edges` must not contain duplicate edges.") + if B.all(oriented_edges[i, :] == oriented_edges[j, ::-1]): + raise ValueError("`oriented_edges` must not contain duplicate edges.") - assert set(range(self.num_nodes)) == set( - B.to_numpy(B.flatten(oriented_edges)) - ), "The oriented_edges array must contain all nodes." + if set(range(self.num_nodes)) != set(B.to_numpy(B.flatten(oriented_edges))): + raise ValueError("`oriented_edges` must contain all nodes.") def _checks_oriented_triangles( self, oriented_triangles: B.Numeric, comprehensive=False @@ -278,29 +270,22 @@ def _checks_oriented_triangles( :param comprehensive: If True, perform more extensive checks. """ - - assert ( - B.rank(oriented_triangles) == 2 - ), "The oriented_triangles array must be 2-dimensional." - - assert ( - B.shape(oriented_triangles)[1] == 3 - ), "oriented_triangles must have shape (*, 3)." - - assert B.dtype(oriented_triangles) == int_like( - oriented_triangles - ), "The oriented_triangles must be an array of integers." - assert B.all( - B.abs(oriented_triangles) >= 1 - ), "The oriented_triangles array must contain only non-zero values." - assert B.all( - B.abs(oriented_triangles) <= self.num_edges - ), "The absolute values in the oriented_triangles array must be <= self.num_edges." - - assert B.all( - B.abs(oriented_triangles) < self.num_edges - ), "The absolute values in the oriented_triangles array must be less than self.num_edges." - assert ( + _check_matrix(oriented_triangles) + if B.shape(oriented_triangles)[1] != 3: + raise ValueError("`oriented_triangles` must have shape (*, 3).") + + if B.dtype(oriented_triangles) != int_like(oriented_triangles): + raise ValueError("`oriented_triangles` must be an array of integers.") + if B.any(B.abs(oriented_triangles)) < 1: + raise ValueError("`oriented_triangles` must contain only non-zero values.") + if B.any(B.abs(oriented_triangles) > self.num_edges): + raise ValueError("Absolute values in `oriented_triangles` array must be less than or equal to `self.num_edges`.") + + if B.any( + B.abs(oriented_triangles) >= self.num_edges + ): + raise ValueError("The absolute values in `oriented_triangles` must be less than `self.num_edges`.") + if not( B.all( B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 1]) != 0 ) @@ -310,16 +295,18 @@ def _checks_oriented_triangles( or B.all( B.abs(oriented_triangles[:, 1]) - B.abs(oriented_triangles[:, 2]) != 0 ) - ), "Triangles must consist of 3 different edges." + ): + raise ValueError("Triangles must consist of 3 different edges.") if comprehensive: num_triangles = oriented_triangles.shape[0] for i in range(num_triangles): for j in range(i + 1, num_triangles): - assert B.any( - oriented_triangles[i, :] != oriented_triangles[j, :] - ), "The oriented_triangles array must not contain duplicate triangles." + if B.all( + oriented_triangles[i, :] == oriented_triangles[j, :] + ): + raise ValueError("The oriented_triangles array must not contain duplicate triangles.") def _checks_compatible( self, @@ -332,44 +319,40 @@ def _checks_compatible( The oriented triangles array. """ - assert B.dtype(self.oriented_edges) == B.dtype( + if B.dtype(self.oriented_edges) != B.dtype( oriented_triangles - ), "The oriented_edges and oriented_triangles arrays must have the same dtype." + ): + raise ValueError("`oriented_edges` and `oriented_triangles` must have the same dtype.") num_triangles = oriented_triangles.shape[0] for t in range(num_triangles): resolved_edges = self.resolve_edges(oriented_triangles[t, :]) - assert ( - resolved_edges[0, 1] == resolved_edges[1, 0] - ), "The edges in the triangle must be connected." - assert ( - resolved_edges[1, 1] == resolved_edges[2, 0] - ), "The edges in the triangle must be connected." - assert ( - resolved_edges[2, 1] == resolved_edges[0, 0] - ), "The edges in the triangle must be connected." + if ( + resolved_edges[0, 1] != resolved_edges[1, 0] + ): + raise ValueError("The edges in the triangle must be connected.") + if resolved_edges[1, 1] != resolved_edges[2, 0]: + raise ValueError("The edges in the triangle must be connected.") + if resolved_edges[2, 1] != resolved_edges[0, 0]: + raise ValueError("The edges in the triangle must be connected.") def _check_index(self, index: csr_matrix): edges = [] for e in range(1, self.oriented_edges.shape[0] + 1): i, j = self.oriented_edges[e - 1, :] - assert ( - index[i, j] == e - ), "The index matrix must be compatible with oriented_edges." - assert ( - index[j, i] == -e - ), "The index matrix must be compatible with oriented_edges." + if index[i, j] != e: + raise ValueError("`index` must be compatible with `oriented_edges`.") + if index[j, i] != -e: + raise ValueError("`index` must be compatible with `oriented_edges`.") edges.append((min(i, j), max(i, j))) for i in range(self.num_nodes): for j in range(i + 1, self.num_nodes): if (i, j) not in edges: - assert ( - index[i, j] == 0 - ), "The index matrix must be compatible with oriented_edges." - assert ( - index[j, i] == 0 - ), "The index matrix must be compatible with oriented_edges." + if index[i, j] != 0: + raise ValueError("`index` must be compatible with `oriented_edges`.") + if index[j, i] != 0: + raise ValueError("`index` must be compatible with `oriented_edges`.") def resolve_edges(self, es: B.Int) -> B.Int: r""" @@ -383,8 +366,9 @@ def resolve_edges(self, es: B.Int) -> B.Int: A 2-dimensional array `result` such that `result[e, :]` is `[i, j]` where \|e\| = (i, j) if e > 0 and \|e\| = (j, i) if e < 0. """ - assert B.rank(es) == 1 - assert B.all(B.abs(es) >= 1) and B.all(B.abs(es) <= self.num_edges) + _check_1_dim_vector(es, "es") + if not (B.all(B.abs(es) >= 1) and B.all(B.abs(es) <= self.num_edges)): + raise ValueError("`abs(es)` must lie in the interval [1, `num_edges`].") result = self.oriented_edges[B.abs(es) - 1] result = B.where(B.expand_dims(es > 0, axis=-1), result, result[:, ::-1]) @@ -403,8 +387,9 @@ def resolve_triangles(self, ts: B.Int) -> B.Int: where i = e1[0], j = e2[0], k = e3[0], and e1, e2, e3 are the oriented edges constituting the triangle `t`. """ - assert B.rank(ts) == 1 - assert B.all(B.abs(ts) >= 0) and B.all(B.abs(ts) < self.num_triangles) + _check_1_dim_vector(ts) + if not (B.all(B.abs(ts) >= 0) and B.all(B.abs(ts) < self.num_triangles)): + raise ValueError("`abs(ts)` must lie in the interval [1, `num_edges`].") edge_indices = B.flatten( self.oriented_triangles[ts] @@ -454,14 +439,15 @@ def from_adjacency( # noqa: C901 f"The adjacency matrix must be a numpy array or a scipy sparse matrix not {type(adjacency_matrix)}. Use `type_reference` to specify the backend." ) - if len(index.shape) != 2: - raise ValueError("Adjacency matrix must be a square matrix.") + _check_matrix(index, "adjacency_matrix") + if B.shape(index)[0] != B.shape(index)[1]: + raise ValueError("`adjacency_matrix` must be a square matrix.") if (abs(index - index.T) > 1e-10).nnz != 0: - raise ValueError("Adjacency matrix must be symmetric.") + raise ValueError("`adjacency_matrix` must be symmetric.") if (index.diagonal() != 0).any(): - raise ValueError("Adjacency matrix must have zeros on the diagonal.") + raise ValueError("`adjacency_matrix` must have zeros on the diagonal.") if np.sum(index.data == 1) + np.sum(index.data == 0) != len(index.data): - raise ValueError("Adjacency matrix can only contain zeros and ones.") + raise ValueError("`adjacency_matrix` can only contain zeros and ones.") number_of_nodes = index.shape[0] number_of_edges = np.sum(index.data) // 2 @@ -478,9 +464,9 @@ def from_adjacency( # noqa: C901 index[i, j] = cur_edge_ind index[j, i] = -index[i, j] cur_edge_ind += 1 - assert ( - cur_edge_ind == number_of_edges + 1 - ) # double check that we have the right number of edges + if cur_edge_ind != number_of_edges + 1: + # double check that we have the right number of edges + raise RuntimeError("This should have never happened, please report a bug at https://github.com/geometric-kernels/GeometricKernels/issues.") oriented_edges = B.cast(dtype_integer(type_reference), oriented_edges) if triangles is None: diff --git a/geometric_kernels/spaces/hypercube_graph.py b/geometric_kernels/spaces/hypercube_graph.py index 2034fead..f81f41ab 100644 --- a/geometric_kernels/spaces/hypercube_graph.py +++ b/geometric_kernels/spaces/hypercube_graph.py @@ -45,7 +45,8 @@ class WalshFunctions(EigenfunctionsWithAdditionTheorem): """ def __init__(self, dim: int, num_levels: int) -> None: - assert num_levels <= dim + 1, "The number of levels should be at most dim+1." + if num_levels > dim + 1: + raise ValueError("The number of levels should be at most `dim`+1.") self.dim = dim self._num_levels = num_levels self._num_eigenfunctions: Optional[int] = None # To be computed when needed. diff --git a/geometric_kernels/spaces/mesh.py b/geometric_kernels/spaces/mesh.py index 122806f7..09e5054d 100644 --- a/geometric_kernels/spaces/mesh.py +++ b/geometric_kernels/spaces/mesh.py @@ -60,7 +60,10 @@ class Mesh(DiscreteSpectrumSpace): def __init__(self, vertices: np.ndarray, faces: np.ndarray): self._vertices = vertices - assert self._vertices.shape[1] == 3 # make sure we all is in R^3. + if B.shape(self._vertices)[1] != 3: + # make sure we are in R^3. + raise ValueError(f"The last dimension (axis) of `_vertices` must be 3.") + self._faces = faces self._eigenvalues = None self._eigenfunctions = None diff --git a/geometric_kernels/spaces/product.py b/geometric_kernels/spaces/product.py index c2b7de1c..e5725574 100644 --- a/geometric_kernels/spaces/product.py +++ b/geometric_kernels/spaces/product.py @@ -215,7 +215,11 @@ def __init__( self.eigenindicies, self.nums_per_level ) - assert self.eigenindicies.shape[-1] == len(self.eigenfunctions) + if self.eigenindices.shape[-1] != len(self.eigenfunctions): + raise ValueError("Expected to have S `eigenfunctions` and `eigenindicies` of shape [L, S], " + "where S is the number of spaces and L is the number of levels, " + f"but got S1={len(self.eigenfunctions)} eigenfunctions and " + f"the tshape of `eigenindicies` is {self.eigenindices.shape}, which is incompatible.") def __call__(self, X: B.Numeric, **kwargs) -> B.Numeric: """ @@ -416,9 +420,8 @@ def __init__( num_levels_per_space: Optional[int] = None, ): for space in spaces: - assert isinstance( - space, DiscreteSpectrumSpace - ), "One of the spaces is not an instance of DiscreteSpectrumSpace." + if not isinstance(space, DiscreteSpectrumSpace): + raise ValueError("One of the spaces is not an instance of DiscreteSpectrumSpace.") self.factor_spaces = spaces # List of length S self.num_levels = num_levels @@ -430,9 +433,8 @@ def __init__( if num_levels_per_space is None: num_levels_per_space = num_levels - assert num_levels <= num_levels_per_space ** len( - spaces - ), "Cannot have more levels than there are possible combinations" + if num_levels > num_levels_per_space ** len(spaces): + raise ValueError("Cannot have more levels than there are possible combinations.") # prefetch the eigenvalues of the subspaces factor_space_eigenvalues = B.stack( @@ -493,7 +495,8 @@ def get_eigenfunctions(self, num: int) -> Eigenfunctions: Number of levels. Cannot be larger than the `num_levels` parameter of the constructor. """ - assert num <= self.num_levels + if num > self.num_levels: + raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") max_level = int(self.factor_space_eigenindices[:num, :].max() + 1) @@ -518,7 +521,8 @@ def get_eigenvalues(self, num: int) -> B.Numeric: :return: (num, 1)-shaped array containing the eigenvalues. """ - assert num <= self.num_levels + if num > self.num_levels: + raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") return self._eigenvalues[:num, None] @@ -535,7 +539,8 @@ def get_repeated_eigenvalues(self, num: int) -> B.Numeric: (J, 1)-shaped array containing the repeated eigenvalues,`J is the resulting number of the repeated eigenvalues. """ - assert num <= self.num_levels + if num > self.num_levels: + raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") eigenfunctions = self.get_eigenfunctions(num) eigenvalues = self._eigenvalues[:num] diff --git a/geometric_kernels/utils/kernel_formulas/euclidean.py b/geometric_kernels/utils/kernel_formulas/euclidean.py index f76f347d..99f78f14 100644 --- a/geometric_kernels/utils/kernel_formulas/euclidean.py +++ b/geometric_kernels/utils/kernel_formulas/euclidean.py @@ -27,7 +27,8 @@ def euclidean_matern_12_kernel( The kernel values evaluated at `r`, an array of shape [...]. """ - assert B.all(r >= 0.0) + if not B.all(r >= 0.0): + raise ValueError("Distances must be non-negative.") return B.exp(-r / lengthscale) @@ -49,7 +50,8 @@ def euclidean_matern_32_kernel( The kernel values evaluated at `r`, an array of shape [...]. """ - assert B.all(r >= 0.0) + if not B.all(r >= 0.0): + raise ValueError("Distances must be non-negative.") sqrt3 = sqrt(3.0) r = r / lengthscale @@ -73,7 +75,8 @@ def euclidean_matern_52_kernel( The kernel values evaluated at `r`, an array of shape [...]. """ - assert B.all(r >= 0.0) + if not B.all(r >= 0.0): + raise ValueError("Distances must be non-negative.") sqrt5 = sqrt(5.0) r = r / lengthscale @@ -97,7 +100,8 @@ def euclidean_rbf_kernel( The kernel values evaluated at `r`, an array of shape [...]. """ - assert B.all(r >= 0.0) + if not B.all(r >= 0.0): + raise ValueError("Distances must be non-negative.") r = r / lengthscale return B.exp(-0.5 * r**2) diff --git a/geometric_kernels/utils/kernel_formulas/hypercube_graph.py b/geometric_kernels/utils/kernel_formulas/hypercube_graph.py index 254d7f23..06ea4b94 100644 --- a/geometric_kernels/utils/kernel_formulas/hypercube_graph.py +++ b/geometric_kernels/utils/kernel_formulas/hypercube_graph.py @@ -11,6 +11,7 @@ from geometric_kernels.lab_extras import float_like from geometric_kernels.utils.utils import hamming_distance +from geometric_kernels.utils.utils import _check_matrix, _check_1_vector def hypercube_graph_heat_kernel( @@ -36,9 +37,12 @@ def hypercube_graph_heat_kernel( if X2 is None: X2 = X - assert lengthscale.shape == (1,) - assert X.ndim == 2 and X2.ndim == 2 - assert X.shape[-1] == X2.shape[-1] + _check_1_vector(lengthscale, "lengthscale") + _check_matrix(X, "X") + _check_matrix(X2, "X2") + + if X.shape[-1] != X2.shape[-1]: + raise ValueError("`X` and `X2` must live in a same-dimensional space.") if normalized_laplacian: d = X.shape[-1] diff --git a/geometric_kernels/utils/kernel_formulas/spd.py b/geometric_kernels/utils/kernel_formulas/spd.py index 842348c1..e628cc93 100644 --- a/geometric_kernels/utils/kernel_formulas/spd.py +++ b/geometric_kernels/utils/kernel_formulas/spd.py @@ -43,8 +43,10 @@ def _spd_heat_kernel_2x2_base( if x2 is None: x2 = x - assert x.shape == (2, 2) - assert x2.shape == (2, 2) + if B.shape(x) != (2, 2): + raise ValueError("`x` must have shape [2, 2].") + if x2.shape != (2, 2): + raise ValueError("`x2` must have shape [2, 2].") cl_1 = np.linalg.cholesky(x) cl_2 = np.linalg.cholesky(x2) @@ -53,7 +55,8 @@ def _spd_heat_kernel_2x2_base( # Note: singular values that np.linalg.svd outputs are sorted, the following # code relies on this fact. H1, H2 = np.log(singular_values[0]), np.log(singular_values[1]) - assert H1 >= H2 + if H1 < H2: + raise RuntimeError("Expected `np.linalg.svd` to return sorted eigenvalues.") r_H_sq = H1 * H1 + H2 * H2 alpha = H1 - H2 diff --git a/geometric_kernels/utils/manifold_utils.py b/geometric_kernels/utils/manifold_utils.py index 42280cac..14f8d664 100644 --- a/geometric_kernels/utils/manifold_utils.py +++ b/geometric_kernels/utils/manifold_utils.py @@ -21,9 +21,11 @@ def minkowski_inner_product(vector_a: B.Numeric, vector_b: B.Numeric) -> B.Numer :return: An [...,]-shaped array of inner products. """ - assert vector_a.shape == vector_b.shape + if B.shape(vector_a) != B.shape(vector_b): + raise ValueError("`vector_a` and `vector_b` must have the shape shapes.") n = vector_a.shape[-1] - 1 - assert n > 0 + if n == 0: + raise ValueError("Must have at least 1 point.") diagonal = from_numpy(vector_a, [-1.0] + [1.0] * n) # (n+1) diagonal = B.cast(B.dtype(vector_a), diagonal) return B.einsum("...i,...i->...", diagonal * vector_a, vector_b) @@ -161,6 +163,7 @@ def tangent_onb(manifold, x): projected_onb_eigvals = projected_onb_eigvals[ambient_dim - manifold_dim :] projected_onb_eigvecs = projected_onb_eigvecs[:, ambient_dim - manifold_dim :] - assert np.all(np.isclose(projected_onb_eigvals, 1.0)) + if not np.all(np.isclose(projected_onb_eigvals, 1.0)): + raise RuntimeError("Expected `projected_onb_eigvals` to be close to 1") return projected_onb_eigvecs diff --git a/geometric_kernels/utils/product.py b/geometric_kernels/utils/product.py index 58baded3..2500fd62 100644 --- a/geometric_kernels/utils/product.py +++ b/geometric_kernels/utils/product.py @@ -4,6 +4,7 @@ from beartype.typing import Dict, List from geometric_kernels.lab_extras import smart_cast +from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector def params_to_params_list( @@ -20,13 +21,16 @@ def params_to_params_list( :param params: Parameters of the product kernel. """ - assert params["lengthscale"].shape == params["nu"].shape - assert len(params["nu"].shape) == 1 + if B.shape(params["lengthscale"]) != B.shape(params["nu"]): + raise ValueError("Shape mismatch between `params[\"lengthscale\"]` and `params[\"nu\"].`") + + _check_1_dim_vector(params["nu"], "params[\"nu\"]") if params["nu"].shape[0] == 1: return [params] * number_of_factors - assert params["nu"].shape[0] == number_of_factors + if B.shape(params["nu"])[0] != number_of_factors: + raise ValueError("Shapes of the kernel parameters `lengthscale`, `nu` must be [`number_of_factors`].") list_of_params: List[Dict[str, B.Numeric]] = [] for i in range(number_of_factors): diff --git a/geometric_kernels/utils/special_functions.py b/geometric_kernels/utils/special_functions.py index b222191c..41189737 100644 --- a/geometric_kernels/utils/special_functions.py +++ b/geometric_kernels/utils/special_functions.py @@ -11,6 +11,7 @@ int_like, take_along_axis, ) +from geometric_kernels.utils.utils import _check_matrix def walsh_function(d: int, combination: List[int], x: B.Bool) -> B.Float: @@ -35,8 +36,9 @@ def walsh_function(d: int, combination: List[int], x: B.Bool) -> B.Float: batch. An array of shape [N]. """ - assert x.ndim == 2 - assert x.shape[-1] == d + _check_matrix(x, "x") + if B.shape(x)[-1] != d: + raise ValueError("`x` must live in `d`-dimensional space.") indices = B.cast(int_like(x), from_numpy(x, combination))[None, :] @@ -91,9 +93,12 @@ def kravchuk_normalized( :return: $G_{d, j, m}/G_{d, j, 0}$ where $G_{d, j, m}$ is the Kravchuk polynomial. """ - assert d > 0 - assert 0 <= j and j <= d - assert B.all(0 <= m) and B.all(m <= d) + if d <= 0: + raise ValueError("`d` must be positive.") + if not (0 <= j and j <= d): + raise ValueError("`j` must lie in the interval [0, d].") + if not (B.all(0 <= m) and B.all(m <= d)): + raise ValueError("`m` must lie in the interval [0, d].") m = B.cast(B.dtype_float(m), m) diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index fc0b8d3d..c46ccb5f 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -326,7 +326,8 @@ def log_binomial(n: B.Int, k: B.Int) -> B.Float: :return: The logarithm of the binomial coefficient binom(n, k). """ - assert B.all(0 <= k <= n) + if not B.all(0 <= k <= n): + raise ValueError("Incorrect parameters of the binomial coefficient.") return B.loggamma(n + 1) - B.loggamma(k + 1) - B.loggamma(n - k + 1) @@ -356,3 +357,36 @@ def binary_vectors_and_subsets(d: int): i += 1 return x, combs + + +def _check_field_in_params(params, field): + """ + Raise an error if `params` does not contain a `field`. + """ + if field not in params: + raise ValueError(f"`params` must contain `{field}`.") + + +def _check_1_vector(x, desc): + """ + Raise an error if `x` is not a 1-vector. + """ + if B.shape(x) != (1,): + raise ValueError(f"`{desc}` must be a 1-vector.") + + +def _check_1_dim_vector(x, desc): + """ + Raise an error if `x` is not a 1-dim vector. + """ + if B.rank(x) != 1: + raise ValueError(f"`{desc}` must be 1-dim vector.") + + +def _check_matrix(x, desc): + """ + Raise an error if `x` is not a matrix. + """ + if B.rank(x) != 2 and B.shape(x)[0] != B.shape(x)[1]: + raise ValueError(f"`{desc}` must be a matrix.") + From 30bdd701a6253f5eeda79e31d5bfa908cce7d501 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 00:38:40 +0300 Subject: [PATCH 02/14] Lint --- geometric_kernels/_logging.py | 2 +- .../feature_maps/deterministic.py | 18 ++++-- .../feature_maps/probability_densities.py | 33 ++++++---- geometric_kernels/kernels/feature_map.py | 15 +++-- .../kernels/hodge_compositional.py | 6 +- geometric_kernels/kernels/karhunen_loeve.py | 29 +++++---- geometric_kernels/kernels/product.py | 22 +++++-- geometric_kernels/spaces/graph.py | 4 +- geometric_kernels/spaces/graph_edges.py | 62 +++++++++++-------- geometric_kernels/spaces/product.py | 32 +++++++--- .../utils/kernel_formulas/euclidean.py | 4 +- .../utils/kernel_formulas/hypercube_graph.py | 7 ++- geometric_kernels/utils/manifold_utils.py | 2 +- geometric_kernels/utils/product.py | 15 +++-- geometric_kernels/utils/utils.py | 7 +-- 15 files changed, 162 insertions(+), 96 deletions(-) diff --git a/geometric_kernels/_logging.py b/geometric_kernels/_logging.py index d7b73c89..624a4ebb 100644 --- a/geometric_kernels/_logging.py +++ b/geometric_kernels/_logging.py @@ -1,4 +1,4 @@ -""" Setup logging """ +"""Setup logging""" import logging diff --git a/geometric_kernels/feature_maps/deterministic.py b/geometric_kernels/feature_maps/deterministic.py index 56714e56..78f6e2bc 100644 --- a/geometric_kernels/feature_maps/deterministic.py +++ b/geometric_kernels/feature_maps/deterministic.py @@ -44,19 +44,27 @@ def __init__( if repeated_eigenvalues_laplacian is None: if eigenfunctions is not None: - raise ValueError("If you provide `eigenfunctions`, you must also provide the corresponding `repeated_eigenvalues_laplacian`.") + raise ValueError( + "If you provide `eigenfunctions`, you must also provide the corresponding `repeated_eigenvalues_laplacian`." + ) repeated_eigenvalues_laplacian = self.space.get_repeated_eigenvalues( self.num_levels ) eigenfunctions = self.space.get_eigenfunctions(self.num_levels) else: if eigenfunctions is None: - raise ValueError("If you provide `repeated_eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`.") + raise ValueError( + "If you provide `repeated_eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`." + ) if repeated_eigenvalues_laplacian.shape != (num_levels, 1): - raise ValueError(f"Expected `repeated_eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {repeated_eigenvalues_laplacian.shape}") + raise ValueError( + f"Expected `repeated_eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {repeated_eigenvalues_laplacian.shape}" + ) if eigenfunctions.num_levels != num_levels: - raise ValueError(f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," - f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}") + raise ValueError( + f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," + f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}" + ) self._repeated_eigenvalues = repeated_eigenvalues_laplacian self._eigenfunctions = eigenfunctions diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index 330789f3..5f7476ce 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -22,7 +22,14 @@ eigvalsh, from_numpy, ) -from geometric_kernels.utils.utils import ordered_pairwise_differences, _check_field_in_params, _check_1_vector, _check_1_dim_vector, _check_matrix +from geometric_kernels.utils.utils import ( + _check_1_dim_vector, + _check_1_vector, + _check_field_in_params, + _check_matrix, + ordered_pairwise_differences, +) + def student_t_sample( key: B.RandomState, @@ -77,7 +84,7 @@ def student_t_sample( _check_1_dim_vector(loc, "loc") _check_matrix(shape, "shape") - + shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) key, z = B.randn(key, dtype, *size, n) @@ -138,11 +145,11 @@ def base_density_sample( similar random state (generator) for any other backend. """ _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") - + _check_1_vector(params["nu"], 'params["nu"]') + nu = params["nu"] L = params["lengthscale"] @@ -333,9 +340,9 @@ def _sample_mixture_matern( Update proposition numbers when the paper gets published. """ _check_1_dim(alpha, "alpha") - m = B.shape(alpha)[0] - 1 + m = B.shape(alpha)[0] - 1 if m < 0: - raise ValueError("The mixture must contain at least 1 component.") + raise ValueError("The mixture must contain at least 1 component.") dtype = B.dtype(lengthscale) js = B.range(dtype, 0, m + 1) if shifted_laplacian: @@ -399,10 +406,10 @@ def hyperbolic_density_sample( random state (generator) for any other backend. """ _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") - + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') + _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") + _check_1_vector(params["nu"], 'params["nu"]') nu = params["nu"] L = params["lengthscale"] @@ -480,11 +487,11 @@ def spd_density_sample( random state (generator) for any other backend. """ _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") + _check_1_vector(params["nu"], 'params["nu"]') _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") - + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') + nu = params["nu"] L = params["lengthscale"] diff --git a/geometric_kernels/kernels/feature_map.py b/geometric_kernels/kernels/feature_map.py index 926a9bdd..f8a506fa 100644 --- a/geometric_kernels/kernels/feature_map.py +++ b/geometric_kernels/kernels/feature_map.py @@ -11,7 +11,11 @@ from geometric_kernels.feature_maps import FeatureMap from geometric_kernels.kernels.base import BaseGeometricKernel from geometric_kernels.spaces.base import Space -from geometric_kernels.utils.utils import make_deterministic, _check_field_in_params, _check_1_vector +from geometric_kernels.utils.utils import ( + _check_1_vector, + _check_field_in_params, + make_deterministic, +) class MaternFeatureMapKernel(BaseGeometricKernel): @@ -109,10 +113,10 @@ def K( **kwargs, ): _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") + _check_1_vector(params["nu"], 'params["nu"]') _, features_X = self.feature_map( X, params, normalize=self.normalize, **kwargs @@ -129,11 +133,10 @@ def K( def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs): _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") - + _check_1_vector(params["nu"], 'params["nu"]') _, features_X = self.feature_map( X, params, normalize=self.normalize, **kwargs diff --git a/geometric_kernels/kernels/hodge_compositional.py b/geometric_kernels/kernels/hodge_compositional.py index 8c5ad608..2d60a2df 100644 --- a/geometric_kernels/kernels/hodge_compositional.py +++ b/geometric_kernels/kernels/hodge_compositional.py @@ -11,7 +11,7 @@ from geometric_kernels.kernels.base import BaseGeometricKernel from geometric_kernels.kernels.karhunen_loeve import MaternKarhunenLoeveKernel from geometric_kernels.spaces import HodgeDiscreteSpectrumSpace -from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector +from geometric_kernels.utils.utils import _check_1_vector, _check_field_in_params class MaternHodgeCompositionalKernel(BaseGeometricKernel): @@ -132,7 +132,7 @@ def K( for key in ("harmonic", "gradient", "curl"): _check_field_in_params(params, key) - _check_1_vector(params[key]["logit"], f"params[\"{key}\"][\"logit\"]") + _check_1_vector(params[key]["logit"], f'params["{key}"]["logit"]') # Copy the parameters to avoid modifying the original dict. params = {key: params[key].copy() for key in ["harmonic", "gradient", "curl"]} @@ -161,7 +161,7 @@ def K_diag( for key in ("harmonic", "gradient", "curl"): _check_field_in_params(params, key) - _check_1_vector(params[key]["logit"], f"params[\"{key}\"][\"logit\"]") + _check_1_vector(params[key]["logit"], f'params["{key}"]["logit"]') # Copy the parameters to avoid modifying the original dict. params = {key: params[key].copy() for key in ["harmonic", "gradient", "curl"]} diff --git a/geometric_kernels/kernels/karhunen_loeve.py b/geometric_kernels/kernels/karhunen_loeve.py index dabf66e8..75468645 100644 --- a/geometric_kernels/kernels/karhunen_loeve.py +++ b/geometric_kernels/kernels/karhunen_loeve.py @@ -11,7 +11,7 @@ from geometric_kernels.lab_extras import from_numpy, is_complex from geometric_kernels.spaces import DiscreteSpectrumSpace from geometric_kernels.spaces.eigenfunctions import Eigenfunctions -from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector +from geometric_kernels.utils.utils import _check_1_vector, _check_field_in_params class MaternKarhunenLoeveKernel(BaseGeometricKernel): @@ -75,17 +75,25 @@ def __init__( if eigenvalues_laplacian is None: if eigenfunctions is not None: - raise ValueError("If you provide `eigenfunctions`, you must also provide the corresponding `eigenvalues_laplacian`.") + raise ValueError( + "If you provide `eigenfunctions`, you must also provide the corresponding `eigenvalues_laplacian`." + ) eigenvalues_laplacian = self.space.get_eigenvalues(self.num_levels) eigenfunctions = self.space.get_eigenfunctions(self.num_levels) else: if eigenfunctions is None: - raise ValueError("If you provide `eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`.") + raise ValueError( + "If you provide `eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`." + ) if eigenvalues_laplacian.shape != (num_levels, 1): - raise ValueError(f"Expected `eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {eigenvalues_laplacian.shape}") + raise ValueError( + f"Expected `eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {eigenvalues_laplacian.shape}" + ) if eigenfunctions.num_levels != num_levels: - raise ValueError(f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," - f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}") + raise ValueError( + f"`num_levels` must coincide with `num_levels` in the provided `eigenfunctions`," + f"but `num_levels`={num_levels} and `eigenfunctions.num_levels`={eigenfunctions.num_levels}" + ) self._eigenvalues_laplacian = eigenvalues_laplacian self._eigenfunctions = eigenfunctions @@ -187,11 +195,11 @@ def eigenvalues(self, params: Dict[str, B.Numeric]) -> B.Numeric: An [L, 1]-shaped array. """ _check_field_in_params(params, "lengthscale") - _check_1_vector(params["lengthscale"], "params[\"lengthscale\"]") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') _check_field_in_params(params, "nu") - _check_1_vector(params["nu"], "params[\"nu\"]") - + _check_1_vector(params["nu"], 'params["nu"]') + spectral_values = self.spectrum( self.eigenvalues_laplacian, nu=params["nu"], @@ -226,7 +234,6 @@ def K( raise ValueError("`params` must contain `nu`.") if params["nu"].shape != (1,): raise ValueError(f"`params['nu']` must be a 1-vector.") - weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions @@ -246,7 +253,7 @@ def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs) -> B.Nume raise ValueError("`params` must contain `nu`.") if params["nu"].shape != (1,): raise ValueError(f"`params['nu']` must be a 1-vector.") - + weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions K_diag = Phi.weighted_outerproduct_diag(weights, X, **kwargs) # [N,] diff --git a/geometric_kernels/kernels/product.py b/geometric_kernels/kernels/product.py index 60d786c7..9ac727af 100644 --- a/geometric_kernels/kernels/product.py +++ b/geometric_kernels/kernels/product.py @@ -64,7 +64,9 @@ def __init__( for kernel in self.kernels: # Make sure there is no product kernel in the list of kernels. if isinstance(kernel, ProductGeometricKernel): - raise NotImplementedError("One of the provided kernels is a product kernel itself.") + raise NotImplementedError( + "One of the provided kernels is a product kernel itself." + ) self.spaces.append(kernel.space) self.element_shapes = [space.element_shape for space in self.spaces] self.element_dtypes = [space.element_dtype for space in self.spaces] @@ -79,11 +81,15 @@ def __init__( i += dim else: if len(dimension_indices) != len(self.kernels): - raise ValueError(f"`dimension_indices` must correspond to `kernels`, but got {len(kernels)} kernels and {len(dimension_indices)} dimension indices.") + raise ValueError( + f"`dimension_indices` must correspond to `kernels`, but got {len(kernels)} kernels and {len(dimension_indices)} dimension indices." + ) for idx_list in dimension_indices: for idx in idx_list: if idx < 0: - raise ValueError(f"Expected all `dimension_indices` to be non-negative.") + raise ValueError( + f"Expected all `dimension_indices` to be non-negative." + ) self.dimension_indices = dimension_indices @@ -106,10 +112,14 @@ def init_params(self) -> Dict[str, B.NPNumeric]: for kernel_idx, kernel in enumerate(self.kernels): cur_params = kernel.init_params() if cur_params["lengthscale"].shape != (1,): - raise ValueError(f"All kernels' `lengthscale`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this.") + raise ValueError( + f"All kernels' `lengthscale`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this." + ) if cur_params["nu"].shape != (1,): - raise ValueError(f"All kernels' `nu`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this.") - + raise ValueError( + f"All kernels' `nu`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this." + ) + nu_list.append(cur_params["nu"]) lengthscale_list.append(cur_params["lengthscale"]) diff --git a/geometric_kernels/spaces/graph.py b/geometric_kernels/spaces/graph.py index 3300e528..fed8e88d 100644 --- a/geometric_kernels/spaces/graph.py +++ b/geometric_kernels/spaces/graph.py @@ -121,7 +121,9 @@ def get_eigensystem(self, num): A tuple of eigenvectors [n, num], eigenvalues [num, 1]. """ if num > self.num_vertices: - raise ValueError("Number of eigenpairs cannot exceed the number of vertices.") + raise ValueError( + "Number of eigenpairs cannot exceed the number of vertices." + ) if num not in self.cache: evals, evecs = eigenpairs(self._laplacian, num) diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index dccd93ec..1d11ff17 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -22,7 +22,7 @@ Eigenfunctions, EigenfunctionsFromEigenvectors, ) -from geometric_kernels.utils.utils import _check_matrix, _check_1_dim_vector +from geometric_kernels.utils.utils import _check_1_dim_vector, _check_matrix class GraphEdges(HodgeDiscreteSpectrumSpace): @@ -241,7 +241,9 @@ def _checks_oriented_edges( if B.any(oriented_edges < 0): raise ValueError("`oriented_edges` must contain only non-negative values.") if B.any(oriented_edges >= self.num_nodes): - raise ValueError("The values in the `oriented_edges` array must be less than `self.num_nodes.`") + raise ValueError( + "The values in the `oriented_edges` array must be less than `self.num_nodes.`" + ) if B.any(oriented_edges[:, 0] - oriented_edges[:, 1] == 0): raise ValueError("Loops are not allowed.") @@ -251,12 +253,16 @@ def _checks_oriented_edges( for i in range(num_edges): for j in range(i + 1, num_edges): if B.all(oriented_edges[i, :] == oriented_edges[j, :]): - raise ValueError("`oriented_edges` must not contain duplicate edges.") + raise ValueError( + "`oriented_edges` must not contain duplicate edges." + ) if B.all(oriented_edges[i, :] == oriented_edges[j, ::-1]): - raise ValueError("`oriented_edges` must not contain duplicate edges.") + raise ValueError( + "`oriented_edges` must not contain duplicate edges." + ) if set(range(self.num_nodes)) != set(B.to_numpy(B.flatten(oriented_edges))): - raise ValueError("`oriented_edges` must contain all nodes.") + raise ValueError("`oriented_edges` must contain all nodes.") def _checks_oriented_triangles( self, oriented_triangles: B.Numeric, comprehensive=False @@ -279,13 +285,15 @@ def _checks_oriented_triangles( if B.any(B.abs(oriented_triangles)) < 1: raise ValueError("`oriented_triangles` must contain only non-zero values.") if B.any(B.abs(oriented_triangles) > self.num_edges): - raise ValueError("Absolute values in `oriented_triangles` array must be less than or equal to `self.num_edges`.") + raise ValueError( + "Absolute values in `oriented_triangles` array must be less than or equal to `self.num_edges`." + ) - if B.any( - B.abs(oriented_triangles) >= self.num_edges - ): - raise ValueError("The absolute values in `oriented_triangles` must be less than `self.num_edges`.") - if not( + if B.any(B.abs(oriented_triangles) >= self.num_edges): + raise ValueError( + "The absolute values in `oriented_triangles` must be less than `self.num_edges`." + ) + if not ( B.all( B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 1]) != 0 ) @@ -303,10 +311,10 @@ def _checks_oriented_triangles( for i in range(num_triangles): for j in range(i + 1, num_triangles): - if B.all( - oriented_triangles[i, :] == oriented_triangles[j, :] - ): - raise ValueError("The oriented_triangles array must not contain duplicate triangles.") + if B.all(oriented_triangles[i, :] == oriented_triangles[j, :]): + raise ValueError( + "The oriented_triangles array must not contain duplicate triangles." + ) def _checks_compatible( self, @@ -319,17 +327,15 @@ def _checks_compatible( The oriented triangles array. """ - if B.dtype(self.oriented_edges) != B.dtype( - oriented_triangles - ): - raise ValueError("`oriented_edges` and `oriented_triangles` must have the same dtype.") + if B.dtype(self.oriented_edges) != B.dtype(oriented_triangles): + raise ValueError( + "`oriented_edges` and `oriented_triangles` must have the same dtype." + ) num_triangles = oriented_triangles.shape[0] for t in range(num_triangles): resolved_edges = self.resolve_edges(oriented_triangles[t, :]) - if ( - resolved_edges[0, 1] != resolved_edges[1, 0] - ): + if resolved_edges[0, 1] != resolved_edges[1, 0]: raise ValueError("The edges in the triangle must be connected.") if resolved_edges[1, 1] != resolved_edges[2, 0]: raise ValueError("The edges in the triangle must be connected.") @@ -350,9 +356,13 @@ def _check_index(self, index: csr_matrix): for j in range(i + 1, self.num_nodes): if (i, j) not in edges: if index[i, j] != 0: - raise ValueError("`index` must be compatible with `oriented_edges`.") + raise ValueError( + "`index` must be compatible with `oriented_edges`." + ) if index[j, i] != 0: - raise ValueError("`index` must be compatible with `oriented_edges`.") + raise ValueError( + "`index` must be compatible with `oriented_edges`." + ) def resolve_edges(self, es: B.Int) -> B.Int: r""" @@ -466,7 +476,9 @@ def from_adjacency( # noqa: C901 cur_edge_ind += 1 if cur_edge_ind != number_of_edges + 1: # double check that we have the right number of edges - raise RuntimeError("This should have never happened, please report a bug at https://github.com/geometric-kernels/GeometricKernels/issues.") + raise RuntimeError( + "This should have never happened, please report a bug at https://github.com/geometric-kernels/GeometricKernels/issues." + ) oriented_edges = B.cast(dtype_integer(type_reference), oriented_edges) if triangles is None: diff --git a/geometric_kernels/spaces/product.py b/geometric_kernels/spaces/product.py index e5725574..bbb78b40 100644 --- a/geometric_kernels/spaces/product.py +++ b/geometric_kernels/spaces/product.py @@ -216,10 +216,12 @@ def __init__( ) if self.eigenindices.shape[-1] != len(self.eigenfunctions): - raise ValueError("Expected to have S `eigenfunctions` and `eigenindicies` of shape [L, S], " - "where S is the number of spaces and L is the number of levels, " - f"but got S1={len(self.eigenfunctions)} eigenfunctions and " - f"the tshape of `eigenindicies` is {self.eigenindices.shape}, which is incompatible.") + raise ValueError( + "Expected to have S `eigenfunctions` and `eigenindicies` of shape [L, S], " + "where S is the number of spaces and L is the number of levels, " + f"but got S1={len(self.eigenfunctions)} eigenfunctions and " + f"the tshape of `eigenindicies` is {self.eigenindices.shape}, which is incompatible." + ) def __call__(self, X: B.Numeric, **kwargs) -> B.Numeric: """ @@ -421,7 +423,9 @@ def __init__( ): for space in spaces: if not isinstance(space, DiscreteSpectrumSpace): - raise ValueError("One of the spaces is not an instance of DiscreteSpectrumSpace.") + raise ValueError( + "One of the spaces is not an instance of DiscreteSpectrumSpace." + ) self.factor_spaces = spaces # List of length S self.num_levels = num_levels @@ -433,8 +437,10 @@ def __init__( if num_levels_per_space is None: num_levels_per_space = num_levels - if num_levels > num_levels_per_space ** len(spaces): - raise ValueError("Cannot have more levels than there are possible combinations.") + if num_levels > num_levels_per_space ** len(spaces): + raise ValueError( + "Cannot have more levels than there are possible combinations." + ) # prefetch the eigenvalues of the subspaces factor_space_eigenvalues = B.stack( @@ -496,7 +502,9 @@ def get_eigenfunctions(self, num: int) -> Eigenfunctions: of the constructor. """ if num > self.num_levels: - raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") + raise ValueError( + "`num` cannot be larger than the `num_levels` provided in the constructor." + ) max_level = int(self.factor_space_eigenindices[:num, :].max() + 1) @@ -522,7 +530,9 @@ def get_eigenvalues(self, num: int) -> B.Numeric: (num, 1)-shaped array containing the eigenvalues. """ if num > self.num_levels: - raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") + raise ValueError( + "`num` cannot be larger than the `num_levels` provided in the constructor." + ) return self._eigenvalues[:num, None] @@ -540,7 +550,9 @@ def get_repeated_eigenvalues(self, num: int) -> B.Numeric: the resulting number of the repeated eigenvalues. """ if num > self.num_levels: - raise ValueError("`num` cannot be larger than the `num_levels` provided in the constructor.") + raise ValueError( + "`num` cannot be larger than the `num_levels` provided in the constructor." + ) eigenfunctions = self.get_eigenfunctions(num) eigenvalues = self._eigenvalues[:num] diff --git a/geometric_kernels/utils/kernel_formulas/euclidean.py b/geometric_kernels/utils/kernel_formulas/euclidean.py index 99f78f14..0a3d762d 100644 --- a/geometric_kernels/utils/kernel_formulas/euclidean.py +++ b/geometric_kernels/utils/kernel_formulas/euclidean.py @@ -76,7 +76,7 @@ def euclidean_matern_52_kernel( """ if not B.all(r >= 0.0): - raise ValueError("Distances must be non-negative.") + raise ValueError("Distances must be non-negative.") sqrt5 = sqrt(5.0) r = r / lengthscale @@ -101,7 +101,7 @@ def euclidean_rbf_kernel( """ if not B.all(r >= 0.0): - raise ValueError("Distances must be non-negative.") + raise ValueError("Distances must be non-negative.") r = r / lengthscale return B.exp(-0.5 * r**2) diff --git a/geometric_kernels/utils/kernel_formulas/hypercube_graph.py b/geometric_kernels/utils/kernel_formulas/hypercube_graph.py index 06ea4b94..783aca08 100644 --- a/geometric_kernels/utils/kernel_formulas/hypercube_graph.py +++ b/geometric_kernels/utils/kernel_formulas/hypercube_graph.py @@ -10,8 +10,11 @@ from beartype.typing import Optional from geometric_kernels.lab_extras import float_like -from geometric_kernels.utils.utils import hamming_distance -from geometric_kernels.utils.utils import _check_matrix, _check_1_vector +from geometric_kernels.utils.utils import ( + _check_1_vector, + _check_matrix, + hamming_distance, +) def hypercube_graph_heat_kernel( diff --git a/geometric_kernels/utils/manifold_utils.py b/geometric_kernels/utils/manifold_utils.py index 14f8d664..bd1dfa9d 100644 --- a/geometric_kernels/utils/manifold_utils.py +++ b/geometric_kernels/utils/manifold_utils.py @@ -1,4 +1,4 @@ -""" Utilities for dealing with manifolds. """ +"""Utilities for dealing with manifolds.""" import lab as B import numpy as np diff --git a/geometric_kernels/utils/product.py b/geometric_kernels/utils/product.py index 2500fd62..c31c5e7c 100644 --- a/geometric_kernels/utils/product.py +++ b/geometric_kernels/utils/product.py @@ -1,10 +1,9 @@ -""" Utilities for dealing with product spaces and product kernels. """ +"""Utilities for dealing with product spaces and product kernels.""" import lab as B from beartype.typing import Dict, List from geometric_kernels.lab_extras import smart_cast -from geometric_kernels.utils.utils import _check_field_in_params, _check_1_vector def params_to_params_list( @@ -22,15 +21,19 @@ def params_to_params_list( Parameters of the product kernel. """ if B.shape(params["lengthscale"]) != B.shape(params["nu"]): - raise ValueError("Shape mismatch between `params[\"lengthscale\"]` and `params[\"nu\"].`") - - _check_1_dim_vector(params["nu"], "params[\"nu\"]") + raise ValueError( + 'Shape mismatch between `params["lengthscale"]` and `params["nu"].`' + ) + + _check_1_dim_vector(params["nu"], 'params["nu"]') if params["nu"].shape[0] == 1: return [params] * number_of_factors if B.shape(params["nu"])[0] != number_of_factors: - raise ValueError("Shapes of the kernel parameters `lengthscale`, `nu` must be [`number_of_factors`].") + raise ValueError( + "Shapes of the kernel parameters `lengthscale`, `nu` must be [`number_of_factors`]." + ) list_of_params: List[Dict[str, B.Numeric]] = [] for i in range(number_of_factors): diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index c46ccb5f..5d6a779d 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -240,7 +240,7 @@ def partition_dominance_cone(partition: Tuple[int, ...]) -> Set[Tuple[int, ...]] def partition_dominance_or_subpartition_cone( - partition: Tuple[int, ...] + partition: Tuple[int, ...], ) -> Set[Tuple[int, ...]]: """ Calculates subpartitions and partitions dominated by a given one and having @@ -386,7 +386,6 @@ def _check_1_dim_vector(x, desc): def _check_matrix(x, desc): """ Raise an error if `x` is not a matrix. - """ + """ if B.rank(x) != 2 and B.shape(x)[0] != B.shape(x)[1]: - raise ValueError(f"`{desc}` must be a matrix.") - + raise ValueError(f"`{desc}` must be a matrix.") From 3bb3da66390b6d122c9620245c843e49f9f83e04 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 01:02:01 +0300 Subject: [PATCH 03/14] Lint --- .../feature_maps/probability_densities.py | 4 +- geometric_kernels/kernels/karhunen_loeve.py | 10 +-- geometric_kernels/kernels/product.py | 4 +- geometric_kernels/spaces/graph_edges.py | 70 ++++++++++--------- geometric_kernels/spaces/mesh.py | 2 +- geometric_kernels/spaces/product.py | 4 +- geometric_kernels/utils/product.py | 1 + 7 files changed, 51 insertions(+), 44 deletions(-) diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index 5f7476ce..886ad719 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -85,6 +85,8 @@ def student_t_sample( _check_1_dim_vector(loc, "loc") _check_matrix(shape, "shape") + n = B.shape(loc)[0] + shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) key, z = B.randn(key, dtype, *size, n) @@ -339,7 +341,7 @@ def _sample_mixture_matern( .. todo:: Update proposition numbers when the paper gets published. """ - _check_1_dim(alpha, "alpha") + _check_1_dim_vector(alpha, "alpha") m = B.shape(alpha)[0] - 1 if m < 0: raise ValueError("The mixture must contain at least 1 component.") diff --git a/geometric_kernels/kernels/karhunen_loeve.py b/geometric_kernels/kernels/karhunen_loeve.py index 75468645..e23ff5a9 100644 --- a/geometric_kernels/kernels/karhunen_loeve.py +++ b/geometric_kernels/kernels/karhunen_loeve.py @@ -148,7 +148,7 @@ def spectrum( :return: The spectrum of the Matérn kernel. """ - _check_1_vector(lenghtscale, "lengthscale") + _check_1_vector(lengthscale, "lengthscale") _check_1_vector(nu, "nu") # Note: 1.0 in safe_nu can be replaced by any finite positive value @@ -228,12 +228,12 @@ def K( if "lengthscale" not in params: raise ValueError("`params` must contain `lengthscale`.") if params["lengthscale"].shape != (1,): - raise ValueError(f"`params['lengthscale']` must be a 1-vector.") + raise ValueError("`params['lengthscale']` must be a 1-vector.") if "nu" not in params: raise ValueError("`params` must contain `nu`.") if params["nu"].shape != (1,): - raise ValueError(f"`params['nu']` must be a 1-vector.") + raise ValueError("`params['nu']` must be a 1-vector.") weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions @@ -247,12 +247,12 @@ def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs) -> B.Nume if "lengthscale" not in params: raise ValueError("`params` must contain `lengthscale`.") if params["lengthscale"].shape != (1,): - raise ValueError(f"`params['lengthscale']` must be a 1-vector.") + raise ValueError("`params['lengthscale']` must be a 1-vector.") if "nu" not in params: raise ValueError("`params` must contain `nu`.") if params["nu"].shape != (1,): - raise ValueError(f"`params['nu']` must be a 1-vector.") + raise ValueError("`params['nu']` must be a 1-vector.") weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions diff --git a/geometric_kernels/kernels/product.py b/geometric_kernels/kernels/product.py index 9ac727af..fc3e464e 100644 --- a/geometric_kernels/kernels/product.py +++ b/geometric_kernels/kernels/product.py @@ -63,7 +63,7 @@ def __init__( self.spaces: List[Space] = [] for kernel in self.kernels: # Make sure there is no product kernel in the list of kernels. - if isinstance(kernel, ProductGeometricKernel): + if not isinstance(kernel.space, Space): # as opposed to List[Space] raise NotImplementedError( "One of the provided kernels is a product kernel itself." ) @@ -88,7 +88,7 @@ def __init__( for idx in idx_list: if idx < 0: raise ValueError( - f"Expected all `dimension_indices` to be non-negative." + "Expected all `dimension_indices` to be non-negative." ) self.dimension_indices = dimension_indices diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index 1d11ff17..79d20b45 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -219,7 +219,7 @@ def _compute_index(num_nodes: int, oriented_edges: B.Numeric) -> csr_matrix: result[oriented_edges[i, 1], oriented_edges[i, 0]] = -i - 1 return result.tocsr() - def _checks_oriented_edges( + def _checks_oriented_edges( # NOQA: C901 self, oriented_edges: B.Numeric, num_nodes: int, comprehensive: bool = False ): """ @@ -247,24 +247,26 @@ def _checks_oriented_edges( if B.any(oriented_edges[:, 0] - oriented_edges[:, 1] == 0): raise ValueError("Loops are not allowed.") - if comprehensive: - num_edges = oriented_edges.shape[0] + if not comprehensive: + return - for i in range(num_edges): - for j in range(i + 1, num_edges): - if B.all(oriented_edges[i, :] == oriented_edges[j, :]): - raise ValueError( - "`oriented_edges` must not contain duplicate edges." - ) - if B.all(oriented_edges[i, :] == oriented_edges[j, ::-1]): - raise ValueError( - "`oriented_edges` must not contain duplicate edges." - ) + num_edges = oriented_edges.shape[0] + + for i in range(num_edges): + for j in range(i + 1, num_edges): + if B.all(oriented_edges[i, :] == oriented_edges[j, :]): + raise ValueError( + "`oriented_edges` must not contain duplicate edges." + ) + if B.all(oriented_edges[i, :] == oriented_edges[j, ::-1]): + raise ValueError( + "`oriented_edges` must not contain duplicate edges." + ) - if set(range(self.num_nodes)) != set(B.to_numpy(B.flatten(oriented_edges))): - raise ValueError("`oriented_edges` must contain all nodes.") + if set(range(self.num_nodes)) != set(B.to_numpy(B.flatten(oriented_edges))): + raise ValueError("`oriented_edges` must contain all nodes.") - def _checks_oriented_triangles( + def _checks_oriented_triangles( # NOQA: C901 self, oriented_triangles: B.Numeric, comprehensive=False ): """ @@ -276,7 +278,7 @@ def _checks_oriented_triangles( :param comprehensive: If True, perform more extensive checks. """ - _check_matrix(oriented_triangles) + _check_matrix(oriented_triangles, "oriented_triangles") if B.shape(oriented_triangles)[1] != 3: raise ValueError("`oriented_triangles` must have shape (*, 3).") @@ -293,28 +295,30 @@ def _checks_oriented_triangles( raise ValueError( "The absolute values in `oriented_triangles` must be less than `self.num_edges`." ) - if not ( - B.all( - B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 1]) != 0 + if ( + B.any( + B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 1]) == 0 ) - or B.all( - B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 2]) != 0 + and B.any( + B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 2]) == 0 ) - or B.all( - B.abs(oriented_triangles[:, 1]) - B.abs(oriented_triangles[:, 2]) != 0 + and B.any( + B.abs(oriented_triangles[:, 1]) - B.abs(oriented_triangles[:, 2]) == 0 ) ): raise ValueError("Triangles must consist of 3 different edges.") - if comprehensive: - num_triangles = oriented_triangles.shape[0] + if not comprehensive: + return - for i in range(num_triangles): - for j in range(i + 1, num_triangles): - if B.all(oriented_triangles[i, :] == oriented_triangles[j, :]): - raise ValueError( - "The oriented_triangles array must not contain duplicate triangles." - ) + num_triangles = oriented_triangles.shape[0] + + for i in range(num_triangles): + for j in range(i + 1, num_triangles): + if B.all(oriented_triangles[i, :] == oriented_triangles[j, :]): + raise ValueError( + "The oriented_triangles array must not contain duplicate triangles." + ) def _checks_compatible( self, @@ -397,7 +401,7 @@ def resolve_triangles(self, ts: B.Int) -> B.Int: where i = e1[0], j = e2[0], k = e3[0], and e1, e2, e3 are the oriented edges constituting the triangle `t`. """ - _check_1_dim_vector(ts) + _check_1_dim_vector(ts, "ts") if not (B.all(B.abs(ts) >= 0) and B.all(B.abs(ts) < self.num_triangles)): raise ValueError("`abs(ts)` must lie in the interval [1, `num_edges`].") diff --git a/geometric_kernels/spaces/mesh.py b/geometric_kernels/spaces/mesh.py index 09e5054d..b761b1b6 100644 --- a/geometric_kernels/spaces/mesh.py +++ b/geometric_kernels/spaces/mesh.py @@ -62,7 +62,7 @@ def __init__(self, vertices: np.ndarray, faces: np.ndarray): self._vertices = vertices if B.shape(self._vertices)[1] != 3: # make sure we are in R^3. - raise ValueError(f"The last dimension (axis) of `_vertices` must be 3.") + raise ValueError("The last dimension (axis) of `_vertices` must be 3.") self._faces = faces self._eigenvalues = None diff --git a/geometric_kernels/spaces/product.py b/geometric_kernels/spaces/product.py index bbb78b40..d4bd8246 100644 --- a/geometric_kernels/spaces/product.py +++ b/geometric_kernels/spaces/product.py @@ -215,12 +215,12 @@ def __init__( self.eigenindicies, self.nums_per_level ) - if self.eigenindices.shape[-1] != len(self.eigenfunctions): + if self.eigenindicies.shape[-1] != len(self.eigenfunctions): raise ValueError( "Expected to have S `eigenfunctions` and `eigenindicies` of shape [L, S], " "where S is the number of spaces and L is the number of levels, " f"but got S1={len(self.eigenfunctions)} eigenfunctions and " - f"the tshape of `eigenindicies` is {self.eigenindices.shape}, which is incompatible." + f"the tshape of `eigenindicies` is {self.eigenindicies.shape}, which is incompatible." ) def __call__(self, X: B.Numeric, **kwargs) -> B.Numeric: diff --git a/geometric_kernels/utils/product.py b/geometric_kernels/utils/product.py index c31c5e7c..7ed9a532 100644 --- a/geometric_kernels/utils/product.py +++ b/geometric_kernels/utils/product.py @@ -4,6 +4,7 @@ from beartype.typing import Dict, List from geometric_kernels.lab_extras import smart_cast +from geometric_kernels.utils.utils import _check_1_dim_vector def params_to_params_list( From 179e9096b872af9337425ddba71a61a49bf0b51a Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 01:24:43 +0300 Subject: [PATCH 04/14] Replace is_bearable(..., SparseArray) with issparse --- geometric_kernels/spaces/graph_edges.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index 79d20b45..a7cbb000 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -4,9 +4,8 @@ import lab as B import numpy as np -from beartype.door import is_bearable from beartype.typing import Dict, List, Optional, Tuple, Union -from scipy.sparse import csr_matrix, lil_matrix +from scipy.sparse import csr_matrix, issparse, lil_matrix from geometric_kernels.lab_extras import ( SparseArray, @@ -446,7 +445,7 @@ def from_adjacency( # noqa: C901 """ if isinstance(adjacency_matrix, np.ndarray): index = csr_matrix(adjacency_matrix, dtype=int) - elif is_bearable(adjacency_matrix, SparseArray): + elif issparse(adjacency_matrix): index = csr_matrix(adjacency_matrix, dtype=int, copy=True) else: raise ValueError( From 7914000db65011eabcd875c259df90950e24ba60 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 11:56:36 +0300 Subject: [PATCH 05/14] Register B.rank for SparseArray --- geometric_kernels/lab_extras/numpy/sparse_extras.py | 1 + 1 file changed, 1 insertion(+) diff --git a/geometric_kernels/lab_extras/numpy/sparse_extras.py b/geometric_kernels/lab_extras/numpy/sparse_extras.py index e4a938ce..a500aa5b 100644 --- a/geometric_kernels/lab_extras/numpy/sparse_extras.py +++ b/geometric_kernels/lab_extras/numpy/sparse_extras.py @@ -90,5 +90,6 @@ def pinv(a: Union[SparseArray]): B.shape.register(lambda a: a.shape, _SparseArray) B.sqrt.register(lambda a: a.sqrt(), _SparseArray) B.any.register(lambda a: bool((a == True).sum()), _SparseArray) # noqa +B.rank.register(lambda a: a.ndim, _SparseArray) B.linear_algebra.pinv.register(pinv, _SparseArray) From 0f9f6a5aef6808c54a022227a0bdd0e4bb0e8561 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 22:19:36 +0300 Subject: [PATCH 06/14] Fix typo --- geometric_kernels/spaces/graph_edges.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index a7cbb000..d9c11022 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -283,7 +283,7 @@ def _checks_oriented_triangles( # NOQA: C901 if B.dtype(oriented_triangles) != int_like(oriented_triangles): raise ValueError("`oriented_triangles` must be an array of integers.") - if B.any(B.abs(oriented_triangles)) < 1: + if B.any(B.abs(oriented_triangles) < 1): raise ValueError("`oriented_triangles` must contain only non-zero values.") if B.any(B.abs(oriented_triangles) > self.num_edges): raise ValueError( From d0764bc49dbe3d284c7c451d8406febc9a167bfb Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Tue, 28 Oct 2025 23:58:03 +0300 Subject: [PATCH 07/14] Use `check_*` instead of explicit `if+raise` --- geometric_kernels/kernels/karhunen_loeve.py | 26 +++++++-------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/geometric_kernels/kernels/karhunen_loeve.py b/geometric_kernels/kernels/karhunen_loeve.py index e23ff5a9..09c29908 100644 --- a/geometric_kernels/kernels/karhunen_loeve.py +++ b/geometric_kernels/kernels/karhunen_loeve.py @@ -225,15 +225,11 @@ def eigenvalues(self, params: Dict[str, B.Numeric]) -> B.Numeric: def K( self, params: Dict[str, B.Numeric], X: B.Numeric, X2: Optional[B.Numeric] = None, **kwargs # type: ignore ) -> B.Numeric: - if "lengthscale" not in params: - raise ValueError("`params` must contain `lengthscale`.") - if params["lengthscale"].shape != (1,): - raise ValueError("`params['lengthscale']` must be a 1-vector.") + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') - if "nu" not in params: - raise ValueError("`params` must contain `nu`.") - if params["nu"].shape != (1,): - raise ValueError("`params['nu']` must be a 1-vector.") + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], 'params["nu"]') weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions @@ -244,15 +240,11 @@ def K( return K def K_diag(self, params: Dict[str, B.Numeric], X: B.Numeric, **kwargs) -> B.Numeric: - if "lengthscale" not in params: - raise ValueError("`params` must contain `lengthscale`.") - if params["lengthscale"].shape != (1,): - raise ValueError("`params['lengthscale']` must be a 1-vector.") - - if "nu" not in params: - raise ValueError("`params` must contain `nu`.") - if params["nu"].shape != (1,): - raise ValueError("`params['nu']` must be a 1-vector.") + _check_field_in_params(params, "lengthscale") + _check_1_vector(params["lengthscale"], 'params["lengthscale"]') + + _check_field_in_params(params, "nu") + _check_1_vector(params["nu"], 'params["nu"]') weights = B.cast(B.dtype(params["nu"]), self.eigenvalues(params)) # [L, 1] Phi = self.eigenfunctions From 1428bfbb76042ff768e84b3c9a5c9a2d2a8318b3 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Thu, 30 Oct 2025 21:12:49 +0300 Subject: [PATCH 08/14] Apply suggestions from code review Co-authored-by: Viacheslav Borovitskiy Signed-off-by: stoprightthere --- geometric_kernels/feature_maps/deterministic.py | 6 +++--- geometric_kernels/feature_maps/probability_densities.py | 5 ++++- geometric_kernels/spaces/product.py | 2 +- geometric_kernels/utils/manifold_utils.py | 2 +- geometric_kernels/utils/utils.py | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/geometric_kernels/feature_maps/deterministic.py b/geometric_kernels/feature_maps/deterministic.py index 78f6e2bc..bbad9c1a 100644 --- a/geometric_kernels/feature_maps/deterministic.py +++ b/geometric_kernels/feature_maps/deterministic.py @@ -45,7 +45,7 @@ def __init__( if repeated_eigenvalues_laplacian is None: if eigenfunctions is not None: raise ValueError( - "If you provide `eigenfunctions`, you must also provide the corresponding `repeated_eigenvalues_laplacian`." + "You must either provide both `repeated_eigenvalues_laplacian` and `eigenfunctions` or none of the two." ) repeated_eigenvalues_laplacian = self.space.get_repeated_eigenvalues( self.num_levels @@ -54,11 +54,11 @@ def __init__( else: if eigenfunctions is None: raise ValueError( - "If you provide `repeated_eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`." + "You must either provide both `repeated_eigenvalues_laplacian` and `eigenfunctions` or none of the two." ) if repeated_eigenvalues_laplacian.shape != (num_levels, 1): raise ValueError( - f"Expected `repeated_eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {repeated_eigenvalues_laplacian.shape}" + f"Expected `repeated_eigenvalues_laplacian` to have shape [num_levels={num_levels}, 1] but got {B.shape(repeated_eigenvalues_laplacian)}." ) if eigenfunctions.num_levels != num_levels: raise ValueError( diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index 886ad719..c8124e07 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -86,7 +86,10 @@ def student_t_sample( _check_matrix(shape, "shape") n = B.shape(loc)[0] - +if tuple(B.shape(shape)) != (n, n): + raise ValueError( + f"`Expected `shape` matrix to have shape [{n}, {n}] but got {B.shape(shape)}." + ) shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) key, z = B.randn(key, dtype, *size, n) diff --git a/geometric_kernels/spaces/product.py b/geometric_kernels/spaces/product.py index d4bd8246..14810deb 100644 --- a/geometric_kernels/spaces/product.py +++ b/geometric_kernels/spaces/product.py @@ -220,7 +220,7 @@ def __init__( "Expected to have S `eigenfunctions` and `eigenindicies` of shape [L, S], " "where S is the number of spaces and L is the number of levels, " f"but got S1={len(self.eigenfunctions)} eigenfunctions and " - f"the tshape of `eigenindicies` is {self.eigenindicies.shape}, which is incompatible." + f"the shape of `eigenindicies` is {self.eigenindicies.shape}, which is incompatible." ) def __call__(self, X: B.Numeric, **kwargs) -> B.Numeric: diff --git a/geometric_kernels/utils/manifold_utils.py b/geometric_kernels/utils/manifold_utils.py index bd1dfa9d..108e24f0 100644 --- a/geometric_kernels/utils/manifold_utils.py +++ b/geometric_kernels/utils/manifold_utils.py @@ -22,7 +22,7 @@ def minkowski_inner_product(vector_a: B.Numeric, vector_b: B.Numeric) -> B.Numer An [...,]-shaped array of inner products. """ if B.shape(vector_a) != B.shape(vector_b): - raise ValueError("`vector_a` and `vector_b` must have the shape shapes.") + raise ValueError("`vector_a` and `vector_b` must have the same shapes.") n = vector_a.shape[-1] - 1 if n == 0: raise ValueError("Must have at least 1 point.") diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index 5d6a779d..8030cc92 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -372,7 +372,7 @@ def _check_1_vector(x, desc): Raise an error if `x` is not a 1-vector. """ if B.shape(x) != (1,): - raise ValueError(f"`{desc}` must be a 1-vector.") + raise ValueError(f"`{desc}` must be of shape (1,).") def _check_1_dim_vector(x, desc): From 63e9a8abc2a12ad9b6bf950918dcb6b32ace2efd Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Fri, 31 Oct 2025 00:22:08 +0300 Subject: [PATCH 09/14] Fix --- geometric_kernels/feature_maps/probability_densities.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index c8124e07..9d7f6f01 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -86,10 +86,11 @@ def student_t_sample( _check_matrix(shape, "shape") n = B.shape(loc)[0] -if tuple(B.shape(shape)) != (n, n): - raise ValueError( - f"`Expected `shape` matrix to have shape [{n}, {n}] but got {B.shape(shape)}." - ) + + if tuple(B.shape(shape)) != (n, n): + raise ValueError( + f"`Expected `shape` matrix to have shape [{n}, {n}] but got {B.shape(shape)}." + ) shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) key, z = B.randn(key, dtype, *size, n) From dcbb9073ef4983721c9efc83a714208158e07a99 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Fri, 31 Oct 2025 00:40:08 +0300 Subject: [PATCH 10/14] _check_1_dim_vector -> _check_rank_1_array Also clearer exception strings. --- .../feature_maps/probability_densities.py | 10 +++++----- geometric_kernels/spaces/graph_edges.py | 6 +++--- geometric_kernels/utils/product.py | 4 ++-- geometric_kernels/utils/utils.py | 14 ++++++++------ 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/geometric_kernels/feature_maps/probability_densities.py b/geometric_kernels/feature_maps/probability_densities.py index 9d7f6f01..97c210a7 100644 --- a/geometric_kernels/feature_maps/probability_densities.py +++ b/geometric_kernels/feature_maps/probability_densities.py @@ -23,10 +23,10 @@ from_numpy, ) from geometric_kernels.utils.utils import ( - _check_1_dim_vector, _check_1_vector, _check_field_in_params, _check_matrix, + _check_rank_1_array, ordered_pairwise_differences, ) @@ -82,14 +82,14 @@ def student_t_sample( """ _check_1_vector(df, "df") - _check_1_dim_vector(loc, "loc") + _check_rank_1_array(loc, "loc") _check_matrix(shape, "shape") n = B.shape(loc)[0] if tuple(B.shape(shape)) != (n, n): raise ValueError( - f"`Expected `shape` matrix to have shape [{n}, {n}] but got {B.shape(shape)}." + f"`Expected `shape` matrix to have shape [{n}, {n}], but got {B.shape(shape)}." ) shape_sqrt = B.chol(shape) dtype = dtype or dtype_double(key) @@ -281,7 +281,7 @@ def _sample_mixture_heat( .. todo:: Update proposition numbers when the paper gets published. """ - _check_1_dim_vector(alpha, "alpha") + _check_rank_1_array(alpha, "alpha") m = B.shape(alpha)[0] - 1 if m < 0: raise ValueError("The mixture must contain at least 1 component.") @@ -345,7 +345,7 @@ def _sample_mixture_matern( .. todo:: Update proposition numbers when the paper gets published. """ - _check_1_dim_vector(alpha, "alpha") + _check_rank_1_array(alpha, "alpha") m = B.shape(alpha)[0] - 1 if m < 0: raise ValueError("The mixture must contain at least 1 component.") diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index d9c11022..f56e8d02 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -21,7 +21,7 @@ Eigenfunctions, EigenfunctionsFromEigenvectors, ) -from geometric_kernels.utils.utils import _check_1_dim_vector, _check_matrix +from geometric_kernels.utils.utils import _check_matrix, _check_rank_1_array class GraphEdges(HodgeDiscreteSpectrumSpace): @@ -379,7 +379,7 @@ def resolve_edges(self, es: B.Int) -> B.Int: A 2-dimensional array `result` such that `result[e, :]` is `[i, j]` where \|e\| = (i, j) if e > 0 and \|e\| = (j, i) if e < 0. """ - _check_1_dim_vector(es, "es") + _check_rank_1_array(es, "es") if not (B.all(B.abs(es) >= 1) and B.all(B.abs(es) <= self.num_edges)): raise ValueError("`abs(es)` must lie in the interval [1, `num_edges`].") @@ -400,7 +400,7 @@ def resolve_triangles(self, ts: B.Int) -> B.Int: where i = e1[0], j = e2[0], k = e3[0], and e1, e2, e3 are the oriented edges constituting the triangle `t`. """ - _check_1_dim_vector(ts, "ts") + _check_rank_1_array(ts, "ts") if not (B.all(B.abs(ts) >= 0) and B.all(B.abs(ts) < self.num_triangles)): raise ValueError("`abs(ts)` must lie in the interval [1, `num_edges`].") diff --git a/geometric_kernels/utils/product.py b/geometric_kernels/utils/product.py index 7ed9a532..cfc182db 100644 --- a/geometric_kernels/utils/product.py +++ b/geometric_kernels/utils/product.py @@ -4,7 +4,7 @@ from beartype.typing import Dict, List from geometric_kernels.lab_extras import smart_cast -from geometric_kernels.utils.utils import _check_1_dim_vector +from geometric_kernels.utils.utils import _check_rank_1_array def params_to_params_list( @@ -26,7 +26,7 @@ def params_to_params_list( 'Shape mismatch between `params["lengthscale"]` and `params["nu"].`' ) - _check_1_dim_vector(params["nu"], 'params["nu"]') + _check_rank_1_array(params["nu"], 'params["nu"]') if params["nu"].shape[0] == 1: return [params] * number_of_factors diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index 8030cc92..7a5c21a1 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -369,18 +369,20 @@ def _check_field_in_params(params, field): def _check_1_vector(x, desc): """ - Raise an error if `x` is not a 1-vector. + Raise an error if `x` is not a vector of shape [1,]. """ if B.shape(x) != (1,): - raise ValueError(f"`{desc}` must be of shape (1,).") + raise ValueError(f"`{desc}` must have shape `[1,]`, but has shape {B.shape(x)}.") -def _check_1_dim_vector(x, desc): +def _check_rank_1_array(x, desc): """ - Raise an error if `x` is not a 1-dim vector. + Raise an error if `x` is not a rank-1 array. """ if B.rank(x) != 1: - raise ValueError(f"`{desc}` must be 1-dim vector.") + raise ValueError( + f"`{desc}` must be have 1 dimension (`ndim` == 1), but has shape {B.shape(x)}." + ) def _check_matrix(x, desc): @@ -388,4 +390,4 @@ def _check_matrix(x, desc): Raise an error if `x` is not a matrix. """ if B.rank(x) != 2 and B.shape(x)[0] != B.shape(x)[1]: - raise ValueError(f"`{desc}` must be a matrix.") + raise ValueError(f"`{desc}` must be a matrix, but has shape {B.shape(x)}.") From 5dcbd05ac4a9b7d792a989684c8bca6ebda3c9bb Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Fri, 31 Oct 2025 00:45:34 +0300 Subject: [PATCH 11/14] Further clarify exceptions --- geometric_kernels/kernels/karhunen_loeve.py | 4 ++-- geometric_kernels/kernels/product.py | 10 ++++++---- geometric_kernels/utils/utils.py | 4 +++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/geometric_kernels/kernels/karhunen_loeve.py b/geometric_kernels/kernels/karhunen_loeve.py index 09c29908..0aa3625d 100644 --- a/geometric_kernels/kernels/karhunen_loeve.py +++ b/geometric_kernels/kernels/karhunen_loeve.py @@ -76,14 +76,14 @@ def __init__( if eigenvalues_laplacian is None: if eigenfunctions is not None: raise ValueError( - "If you provide `eigenfunctions`, you must also provide the corresponding `eigenvalues_laplacian`." + "You must either provide both `eigenfunctions` and `eigenvalues_laplacian`, or none of the two." ) eigenvalues_laplacian = self.space.get_eigenvalues(self.num_levels) eigenfunctions = self.space.get_eigenfunctions(self.num_levels) else: if eigenfunctions is None: raise ValueError( - "If you provide `eigenvalues_laplacian`, you must also provide the corresponding `eigenfunctions`." + "You must either provide both `eigenfunctions` and `eigenvalues_laplacian`, or none of the two." ) if eigenvalues_laplacian.shape != (num_levels, 1): raise ValueError( diff --git a/geometric_kernels/kernels/product.py b/geometric_kernels/kernels/product.py index fc3e464e..85ce7d30 100644 --- a/geometric_kernels/kernels/product.py +++ b/geometric_kernels/kernels/product.py @@ -111,13 +111,15 @@ def init_params(self) -> Dict[str, B.NPNumeric]: for kernel_idx, kernel in enumerate(self.kernels): cur_params = kernel.init_params() - if cur_params["lengthscale"].shape != (1,): + if B.shape(cur_params["lengthscale"]) != (1,): raise ValueError( - f"All kernels' `lengthscale`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this." + f"All kernels' `lengthscale`s must be have shape [1,], but {kernel_idx}th kernel " + f"({kernel}) violates this with shape {B.shape(cur_params['lengthscale'])}." ) - if cur_params["nu"].shape != (1,): + if B.shape(cur_params["nu"]) != (1,): raise ValueError( - f"All kernels' `nu`s must be 1-vectors, but {kernel_idx}th kernel ({kernel}) violates this." + f"All kernels' `nu`s must be have [1,], but {kernel_idx}th kernel " + f"({kernel}) violates this with shape {B.shape(cur_params['nu'])}." ) nu_list.append(cur_params["nu"]) diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index 7a5c21a1..3ebb42aa 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -372,7 +372,9 @@ def _check_1_vector(x, desc): Raise an error if `x` is not a vector of shape [1,]. """ if B.shape(x) != (1,): - raise ValueError(f"`{desc}` must have shape `[1,]`, but has shape {B.shape(x)}.") + raise ValueError( + f"`{desc}` must have shape `[1,]`, but has shape {B.shape(x)}." + ) def _check_rank_1_array(x, desc): From 3b29233c9324e5b9f28b3cf6572c45ea7d3ab56b Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Fri, 31 Oct 2025 00:48:25 +0300 Subject: [PATCH 12/14] Remove a wrong check in GraphEdges --- geometric_kernels/spaces/graph_edges.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/geometric_kernels/spaces/graph_edges.py b/geometric_kernels/spaces/graph_edges.py index f56e8d02..ae603115 100644 --- a/geometric_kernels/spaces/graph_edges.py +++ b/geometric_kernels/spaces/graph_edges.py @@ -290,10 +290,6 @@ def _checks_oriented_triangles( # NOQA: C901 "Absolute values in `oriented_triangles` array must be less than or equal to `self.num_edges`." ) - if B.any(B.abs(oriented_triangles) >= self.num_edges): - raise ValueError( - "The absolute values in `oriented_triangles` must be less than `self.num_edges`." - ) if ( B.any( B.abs(oriented_triangles[:, 0]) - B.abs(oriented_triangles[:, 1]) == 0 From f75014c442109321632e446bc49d9af86844d2a3 Mon Sep 17 00:00:00 2001 From: stoprightthere Date: Fri, 31 Oct 2025 00:59:00 +0300 Subject: [PATCH 13/14] Fix _check_matrix --- geometric_kernels/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index 3ebb42aa..dd1d594f 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -391,5 +391,5 @@ def _check_matrix(x, desc): """ Raise an error if `x` is not a matrix. """ - if B.rank(x) != 2 and B.shape(x)[0] != B.shape(x)[1]: + if B.rank(x) != 2: raise ValueError(f"`{desc}` must be a matrix, but has shape {B.shape(x)}.") From c65e1eac911dfee335d4d8fb8c359b8b92b5b2b6 Mon Sep 17 00:00:00 2001 From: Viacheslav Borovitskiy Date: Thu, 30 Oct 2025 22:08:59 +0000 Subject: [PATCH 14/14] Exception string typo fix Signed-off-by: Viacheslav Borovitskiy --- geometric_kernels/utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geometric_kernels/utils/utils.py b/geometric_kernels/utils/utils.py index dd1d594f..452ca8ef 100644 --- a/geometric_kernels/utils/utils.py +++ b/geometric_kernels/utils/utils.py @@ -383,7 +383,7 @@ def _check_rank_1_array(x, desc): """ if B.rank(x) != 1: raise ValueError( - f"`{desc}` must be have 1 dimension (`ndim` == 1), but has shape {B.shape(x)}." + f"`{desc}` must have 1 dimension (`ndim` == 1), but has shape {B.shape(x)}." )