From b1d5e2f8588fcc34801b05a09f80ce6c37a5ab1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthieu=20Bult=C3=A9?= Date: Wed, 22 Jun 2022 09:59:59 +0200 Subject: [PATCH 1/2] Handle ops between FDGrid and Python functions --- skfda/representation/grid.py | 23 +++++++++++++---------- tests/test_grid.py | 7 +++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 1d9598121..1bef1090d 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -13,6 +13,7 @@ from typing import ( TYPE_CHECKING, Any, + Callable, Iterable, Optional, Sequence, @@ -710,12 +711,11 @@ def __eq__(self, other: object) -> NDArrayBool: # type: ignore[override] def _get_op_matrix( self, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> Union[None, float, NDArrayFloat, NDArrayInt]: if isinstance(other, numbers.Real): return float(other) elif isinstance(other, np.ndarray): - if other.shape in {(), (1,)}: return other elif other.shape == (self.n_samples,): @@ -741,11 +741,14 @@ def _get_op_matrix( self._check_same_dimensions(other) return other.data_matrix + elif isinstance(other, Callable): + return np.array([[other(x) for x in _gp] for _gp in self.grid_points]).T + return None def __add__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) @@ -756,14 +759,14 @@ def __add__( def __radd__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: return self.__add__(other) def __sub__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) @@ -774,7 +777,7 @@ def __sub__( def __rsub__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) @@ -785,7 +788,7 @@ def __rsub__( def __mul__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) @@ -796,14 +799,14 @@ def __mul__( def __rmul__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: return self.__mul__(other) def __truediv__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) @@ -814,7 +817,7 @@ def __truediv__( def __rtruediv__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], ) -> T: data_matrix = self._get_op_matrix(other) diff --git a/tests/test_grid.py b/tests/test_grid.py index a0daa09e4..ee5203700 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -7,7 +7,6 @@ import numpy as np - class TestFDataGrid(unittest.TestCase): # def setUp(self): could be defined for set up before any test @@ -151,7 +150,7 @@ def test_coordinates(self): fd.data_matrix) def test_add(self): - fd1 = FDataGrid([[1, 2, 3, 4], [2, 3, 4, 5]]) + fd1 = FDataGrid([[1, 2, 3, 4], [2, 3, 4, 5]], [0, 1, 2, 3]) fd2 = fd1 + fd1 np.testing.assert_array_equal(fd2.data_matrix[..., 0], @@ -161,6 +160,10 @@ def test_add(self): np.testing.assert_array_equal(fd2.data_matrix[..., 0], [[3, 4, 5, 6], [4, 5, 6, 7]]) + fd2 = fd1 + (lambda x: x) + np.testing.assert_array_equal(fd2.data_matrix[..., 0], + [[1, 3, 5, 7], [2, 4, 6, 8]]) + fd2 = fd1 + np.array(2) np.testing.assert_array_equal(fd2.data_matrix[..., 0], [[3, 4, 5, 6], [4, 5, 6, 7]]) From 31b357e80438f6bf38545c3ca6f164df2f0135f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthieu=20Bult=C3=A9?= Date: Wed, 22 Jun 2022 16:25:28 +0200 Subject: [PATCH 2/2] Handle ops between vector-valued FDGrid and Python functions --- skfda/representation/grid.py | 21 +++++++++++---------- tests/test_grid.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/skfda/representation/grid.py b/skfda/representation/grid.py index 1bef1090d..8306db74b 100644 --- a/skfda/representation/grid.py +++ b/skfda/representation/grid.py @@ -711,7 +711,7 @@ def __eq__(self, other: object) -> NDArrayBool: # type: ignore[override] def _get_op_matrix( self, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> Union[None, float, NDArrayFloat, NDArrayInt]: if isinstance(other, numbers.Real): return float(other) @@ -742,13 +742,14 @@ def _get_op_matrix( return other.data_matrix elif isinstance(other, Callable): - return np.array([[other(x) for x in _gp] for _gp in self.grid_points]).T + coordinates = np.array(np.meshgrid(*self.grid_points, indexing='ij')).T.reshape(-1, self.dim_domain) + return np.array([other(x) for x in coordinates]).reshape((1,) + self.data_matrix.shape[1:]) return None def __add__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) @@ -759,14 +760,14 @@ def __add__( def __radd__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: return self.__add__(other) def __sub__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) @@ -777,7 +778,7 @@ def __sub__( def __rsub__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) @@ -788,7 +789,7 @@ def __rsub__( def __mul__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) @@ -799,14 +800,14 @@ def __mul__( def __rmul__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: return self.__mul__(other) def __truediv__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) @@ -817,7 +818,7 @@ def __truediv__( def __rtruediv__( self: T, - other: Union[T, NDArrayFloat, NDArrayInt, float, Callable], + other: Union[T, NDArrayFloat, NDArrayInt, float, Callable[[Union[float, NDArrayFloat]], Union[float, NDArrayFloat]]], ) -> T: data_matrix = self._get_op_matrix(other) diff --git a/tests/test_grid.py b/tests/test_grid.py index ee5203700..5cb6df087 100644 --- a/tests/test_grid.py +++ b/tests/test_grid.py @@ -151,6 +151,21 @@ def test_coordinates(self): def test_add(self): fd1 = FDataGrid([[1, 2, 3, 4], [2, 3, 4, 5]], [0, 1, 2, 3]) + fd_2d_in = FDataGrid([ + [[[0], [0]], [[0], [0]]], + [[[1], [1]], [[1], [1]]]], + [[0, 1], [0, 1]] + ) + fd_2d_out = FDataGrid([ + [[0, 0], [1, 1]], + [[1, 1], [0, 0]]], + [[0, 1]] + ) + fd_2d_in_out = FDataGrid([ + [[[0, 0], [0, 1]], [[1, 0], [1, 1]]], + [[[1, 1], [1, 0]], [[0, 1], [0, 0]]]], + [[0, 1], [0, 1]] + ) fd2 = fd1 + fd1 np.testing.assert_array_equal(fd2.data_matrix[..., 0], @@ -164,6 +179,24 @@ def test_add(self): np.testing.assert_array_equal(fd2.data_matrix[..., 0], [[1, 3, 5, 7], [2, 4, 6, 8]]) + fd2 = fd_2d_in + np.max + np.testing.assert_array_equal(fd2.data_matrix[..., 0], [ + [[0, 1], [1, 1]], + [[1, 2], [2, 2]] + ]) + + fd2 = fd_2d_out + (lambda x: np.array([x, x])) + np.testing.assert_array_equal(fd2.data_matrix, [ + [[0, 0], [2, 2]], + [[1, 1], [1, 1]] + ]) + + fd2 = fd_2d_in_out + (lambda x: x) + np.testing.assert_array_equal(fd2.data_matrix, [ + [[[0, 0], [1, 1]], [[1, 1], [2, 2]]], + [[[1, 1], [2, 0]], [[0, 2], [1, 1]]] + ]) + fd2 = fd1 + np.array(2) np.testing.assert_array_equal(fd2.data_matrix[..., 0], [[3, 4, 5, 6], [4, 5, 6, 7]])