From 3a208c43c7d09618b1ceca7589cb7daeaf42afe5 Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Tue, 28 Apr 2020 20:25:33 +0200 Subject: [PATCH 1/6] Add ConcatLibrary to libraries. --- pysindy/feature_library/__init__.py | 1 + pysindy/feature_library/concat_library.py | 40 ++++++++++++++++++++ test/feature_library/test_feature_library.py | 12 ++++++ 3 files changed, 53 insertions(+) create mode 100644 pysindy/feature_library/concat_library.py diff --git a/pysindy/feature_library/__init__.py b/pysindy/feature_library/__init__.py index f85b194ce..f703e0cb7 100644 --- a/pysindy/feature_library/__init__.py +++ b/pysindy/feature_library/__init__.py @@ -2,3 +2,4 @@ from .fourier_library import FourierLibrary from .identity_library import IdentityLibrary from .polynomial_library import PolynomialLibrary +from .concat_library import ConcatLibrary diff --git a/pysindy/feature_library/concat_library.py b/pysindy/feature_library/concat_library.py new file mode 100644 index 000000000..9b6f0c13f --- /dev/null +++ b/pysindy/feature_library/concat_library.py @@ -0,0 +1,40 @@ +import numpy as np + +from .feature_library import BaseFeatureLibrary + +class ConcatLibrary(BaseFeatureLibrary): + + def __init__(self, libraries: list): + super(ConcatLibrary, self).__init__() + + self._libraries = libraries + + def fit(self, X, y=None): + # first fit all libs below + fitted_libs = [lib.fit(X, y) for lib in self._libraries] + self.n_output_features_ = sum([lib.n_output_features_ for lib in fitted_libs]) + self._libraries = fitted_libs + return self + + def transform(self, X): + + n_samples = X.shape[0] + + XP = np.zeros((n_samples, self.n_output_features_)) + + current_feat = 0 + for lib in self._libraries: + lib_n_output_features = lib.n_output_features_ + + XP[:, current_feat:current_feat+lib_n_output_features] = lib.transform(X) + + current_feat += lib_n_output_features + + return XP + + def get_feature_names(self, input_features=None): + feature_names = list() + for lib in self._libraries: + lib_feat_names = lib.get_feature_names(input_features) + feature_names += lib_feat_names + return feature_names diff --git a/test/feature_library/test_feature_library.py b/test/feature_library/test_feature_library.py index 95f545c23..b48669c09 100644 --- a/test/feature_library/test_feature_library.py +++ b/test/feature_library/test_feature_library.py @@ -12,6 +12,7 @@ from pysindy.feature_library import FourierLibrary from pysindy.feature_library import IdentityLibrary from pysindy.feature_library import PolynomialLibrary +from pysindy.feature_library import ConcatLibrary from pysindy.feature_library.feature_library import BaseFeatureLibrary @@ -59,6 +60,7 @@ def test_bad_parameters(): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), + ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), pytest.lazy_fixture("data_custom_library"), ], ) @@ -74,6 +76,7 @@ def test_fit_transform(data_lorenz, library): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), + ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), pytest.lazy_fixture("data_custom_library"), ], ) @@ -89,6 +92,7 @@ def test_change_in_data_shape(data_lorenz, library): [ (IdentityLibrary(), 3), (PolynomialLibrary(), 10), + (ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), 13), (FourierLibrary(), 6), (pytest.lazy_fixture("data_custom_library"), 9), ], @@ -107,6 +111,7 @@ def test_output_shape(data_lorenz, library, shape): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), + ConcatLibrary([PolynomialLibrary(), FourierLibrary()]), pytest.lazy_fixture("data_custom_library"), ], ) @@ -174,3 +179,10 @@ def test_not_implemented(data_lorenz): with pytest.raises(NotImplementedError): library.get_feature_names(x) + + +def test_concat(): + ident_lib = IdentityLibrary() + poly_lib = PolynomialLibrary() + concat_lib = ConcatLibrary([ident_lib, poly_lib]) + assert isinstance(concat_lib, ConcatLibrary) From 3aa3d9921aedb4bcc0c83cfefa3ea273a7a8ad2f Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Wed, 29 Apr 2020 17:24:29 +0200 Subject: [PATCH 2/6] Add documentation. --- pysindy/feature_library/concat_library.py | 97 +++++++++++++++++++++-- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/pysindy/feature_library/concat_library.py b/pysindy/feature_library/concat_library.py index 9b6f0c13f..da4995032 100644 --- a/pysindy/feature_library/concat_library.py +++ b/pysindy/feature_library/concat_library.py @@ -2,39 +2,120 @@ from .feature_library import BaseFeatureLibrary + class ConcatLibrary(BaseFeatureLibrary): + """Concatenate multiple libraries into one library. All settings + provided to individual libraries will be applied. + + Parameters + ---------- + libraries : list of libraries + Library instances to be applied to the input matrix. + + Attributes + ---------- + libraries_ : list of libraries + Library instances to be applied to the input matrix. + + n_output_features_ : int + The total number of output features. The number of output features + is the product of the number of library functions and the number of + input features. + + Examples + -------- + >>> import numpy as np + >>> from pysindy.feature_library import FourierLibrary, CustomLibrary + >>> from pysindy.feature_library import ConcatLibrary + >>> X = np.array([[0.,-1],[1.,0.],[2.,-1.]]) + >>> functions = [lambda x : np.exp(x), lambda x,y : np.sin(x+y)] + >>> lib_custom = CustomLibrary(library_functions=functions) + >>> lib_fourier = FourierLibrary() + >>> lib_concat = ConcatLibrary([lib_custom, lib_fourier]) + >>> lib_concat.fit() + >>> lib.transform(X) + """ def __init__(self, libraries: list): super(ConcatLibrary, self).__init__() - - self._libraries = libraries + self.libraries_ = libraries def fit(self, X, y=None): - # first fit all libs below - fitted_libs = [lib.fit(X, y) for lib in self._libraries] + """ + Compute number of output features. + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + The data. + + Returns + ------- + self : instance + """ + + # first fit all libs provided below + fitted_libs = [lib.fit(X, y) for lib in self.libraries_] + + # calculate the sum of output features self.n_output_features_ = sum([lib.n_output_features_ for lib in fitted_libs]) - self._libraries = fitted_libs + + # save fitted libs + self.libraries_ = fitted_libs + return self def transform(self, X): + """Transform data with libs provided below. + + Parameters + ---------- + X : array-like, shape [n_samples, n_features] + The data to transform, row by row. + + Returns + ------- + XP : np.ndarray, shape [n_samples, NP] + The matrix of features, where NP is the number of features + generated from applying the custom functions to the inputs. + + """ n_samples = X.shape[0] + # preallocate matrix XP = np.zeros((n_samples, self.n_output_features_)) current_feat = 0 - for lib in self._libraries: + for lib in self.libraries_: + + # retrieve num features from lib lib_n_output_features = lib.n_output_features_ - XP[:, current_feat:current_feat+lib_n_output_features] = lib.transform(X) + start_feature_index = current_feat + end_feature_index = start_feature_index + lib_n_output_features + + XP[:, start_feature_index:end_feature_index] = lib.transform(X) current_feat += lib_n_output_features return XP def get_feature_names(self, input_features=None): + """Return feature names for output features. + + Parameters + ---------- + input_features : list of string, length n_features, optional + String names for input features if available. By default, + "x0", "x1", ... "xn_features" is used. + + Returns + ------- + output_feature_names : list of string, length n_output_features + """ feature_names = list() - for lib in self._libraries: + for lib in self.libraries_: lib_feat_names = lib.get_feature_names(input_features) feature_names += lib_feat_names return feature_names From 7c34d28114cf4bbf90e0ef129a42524f1d24e809 Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Wed, 29 Apr 2020 17:32:00 +0200 Subject: [PATCH 3/6] Reorder imports. --- pysindy/feature_library/__init__.py | 2 +- test/feature_library/test_feature_library.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pysindy/feature_library/__init__.py b/pysindy/feature_library/__init__.py index f703e0cb7..f9869eb98 100644 --- a/pysindy/feature_library/__init__.py +++ b/pysindy/feature_library/__init__.py @@ -1,5 +1,5 @@ +from .concat_library import ConcatLibrary from .custom_library import CustomLibrary from .fourier_library import FourierLibrary from .identity_library import IdentityLibrary from .polynomial_library import PolynomialLibrary -from .concat_library import ConcatLibrary diff --git a/test/feature_library/test_feature_library.py b/test/feature_library/test_feature_library.py index b48669c09..e3d5a3019 100644 --- a/test/feature_library/test_feature_library.py +++ b/test/feature_library/test_feature_library.py @@ -8,11 +8,11 @@ from sklearn.exceptions import NotFittedError from sklearn.utils.validation import check_is_fitted +from pysindy.feature_library import ConcatLibrary from pysindy.feature_library import CustomLibrary from pysindy.feature_library import FourierLibrary from pysindy.feature_library import IdentityLibrary from pysindy.feature_library import PolynomialLibrary -from pysindy.feature_library import ConcatLibrary from pysindy.feature_library.feature_library import BaseFeatureLibrary From 789a4b5a712451e5b156328f025726c2aef01e58 Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Thu, 30 Apr 2020 14:19:06 +0200 Subject: [PATCH 4/6] Move ConcatLibrary to feature_library.py and add a __add__ method to baseclass. --- pysindy/feature_library/__init__.py | 2 +- pysindy/feature_library/concat_library.py | 121 ------------------ pysindy/feature_library/feature_library.py | 123 +++++++++++++++++++ test/feature_library/test_feature_library.py | 6 +- 4 files changed, 127 insertions(+), 125 deletions(-) delete mode 100644 pysindy/feature_library/concat_library.py diff --git a/pysindy/feature_library/__init__.py b/pysindy/feature_library/__init__.py index f9869eb98..1a29f80a2 100644 --- a/pysindy/feature_library/__init__.py +++ b/pysindy/feature_library/__init__.py @@ -1,4 +1,4 @@ -from .concat_library import ConcatLibrary +from .feature_library import ConcatLibrary from .custom_library import CustomLibrary from .fourier_library import FourierLibrary from .identity_library import IdentityLibrary diff --git a/pysindy/feature_library/concat_library.py b/pysindy/feature_library/concat_library.py deleted file mode 100644 index da4995032..000000000 --- a/pysindy/feature_library/concat_library.py +++ /dev/null @@ -1,121 +0,0 @@ -import numpy as np - -from .feature_library import BaseFeatureLibrary - - -class ConcatLibrary(BaseFeatureLibrary): - """Concatenate multiple libraries into one library. All settings - provided to individual libraries will be applied. - - Parameters - ---------- - libraries : list of libraries - Library instances to be applied to the input matrix. - - Attributes - ---------- - libraries_ : list of libraries - Library instances to be applied to the input matrix. - - n_output_features_ : int - The total number of output features. The number of output features - is the product of the number of library functions and the number of - input features. - - Examples - -------- - >>> import numpy as np - >>> from pysindy.feature_library import FourierLibrary, CustomLibrary - >>> from pysindy.feature_library import ConcatLibrary - >>> X = np.array([[0.,-1],[1.,0.],[2.,-1.]]) - >>> functions = [lambda x : np.exp(x), lambda x,y : np.sin(x+y)] - >>> lib_custom = CustomLibrary(library_functions=functions) - >>> lib_fourier = FourierLibrary() - >>> lib_concat = ConcatLibrary([lib_custom, lib_fourier]) - >>> lib_concat.fit() - >>> lib.transform(X) - """ - - def __init__(self, libraries: list): - super(ConcatLibrary, self).__init__() - self.libraries_ = libraries - - def fit(self, X, y=None): - """ - Compute number of output features. - - Parameters - ---------- - X : array-like, shape (n_samples, n_features) - The data. - - Returns - ------- - self : instance - """ - - # first fit all libs provided below - fitted_libs = [lib.fit(X, y) for lib in self.libraries_] - - # calculate the sum of output features - self.n_output_features_ = sum([lib.n_output_features_ for lib in fitted_libs]) - - # save fitted libs - self.libraries_ = fitted_libs - - return self - - def transform(self, X): - """Transform data with libs provided below. - - Parameters - ---------- - X : array-like, shape [n_samples, n_features] - The data to transform, row by row. - - Returns - ------- - XP : np.ndarray, shape [n_samples, NP] - The matrix of features, where NP is the number of features - generated from applying the custom functions to the inputs. - - """ - - n_samples = X.shape[0] - - # preallocate matrix - XP = np.zeros((n_samples, self.n_output_features_)) - - current_feat = 0 - for lib in self.libraries_: - - # retrieve num features from lib - lib_n_output_features = lib.n_output_features_ - - start_feature_index = current_feat - end_feature_index = start_feature_index + lib_n_output_features - - XP[:, start_feature_index:end_feature_index] = lib.transform(X) - - current_feat += lib_n_output_features - - return XP - - def get_feature_names(self, input_features=None): - """Return feature names for output features. - - Parameters - ---------- - input_features : list of string, length n_features, optional - String names for input features if available. By default, - "x0", "x1", ... "xn_features" is used. - - Returns - ------- - output_feature_names : list of string, length n_output_features - """ - feature_names = list() - for lib in self.libraries_: - lib_feat_names = lib.get_feature_names(input_features) - feature_names += lib_feat_names - return feature_names diff --git a/pysindy/feature_library/feature_library.py b/pysindy/feature_library/feature_library.py index 9c7de7e27..67e2f7b4a 100644 --- a/pysindy/feature_library/feature_library.py +++ b/pysindy/feature_library/feature_library.py @@ -6,6 +6,8 @@ from sklearn.base import TransformerMixin from sklearn.utils.validation import check_is_fitted +import numpy as np + class BaseFeatureLibrary(TransformerMixin): """ @@ -71,7 +73,128 @@ def get_feature_names(self, input_features=None): """ raise NotImplementedError + def __add__(self, other): + return ConcatLibrary([self, other]) + @property def size(self): check_is_fitted(self) return self.n_output_features_ + + +class ConcatLibrary(BaseFeatureLibrary): + """Concatenate multiple libraries into one library. All settings + provided to individual libraries will be applied. + + Parameters + ---------- + libraries : list of libraries + Library instances to be applied to the input matrix. + + Attributes + ---------- + libraries_ : list of libraries + Library instances to be applied to the input matrix. + + n_output_features_ : int + The total number of output features. The number of output features + is the product of the number of library functions and the number of + input features. + + Examples + -------- + >>> import numpy as np + >>> from pysindy.feature_library import FourierLibrary, CustomLibrary + >>> from pysindy.feature_library import ConcatLibrary + >>> X = np.array([[0.,-1],[1.,0.],[2.,-1.]]) + >>> functions = [lambda x : np.exp(x), lambda x,y : np.sin(x+y)] + >>> lib_custom = CustomLibrary(library_functions=functions) + >>> lib_fourier = FourierLibrary() + >>> lib_concat = ConcatLibrary([lib_custom, lib_fourier]) + >>> lib_concat.fit() + >>> lib.transform(X) + """ + + def __init__(self, libraries: list): + super(ConcatLibrary, self).__init__() + self.libraries_ = libraries + + def fit(self, X, y=None): + """ + Compute number of output features. + + Parameters + ---------- + X : array-like, shape (n_samples, n_features) + The data. + + Returns + ------- + self : instance + """ + + # first fit all libs provided below + fitted_libs = [lib.fit(X, y) for lib in self.libraries_] + + # calculate the sum of output features + self.n_output_features_ = sum([lib.n_output_features_ for lib in fitted_libs]) + + # save fitted libs + self.libraries_ = fitted_libs + + return self + + def transform(self, X): + """Transform data with libs provided below. + + Parameters + ---------- + X : array-like, shape [n_samples, n_features] + The data to transform, row by row. + + Returns + ------- + XP : np.ndarray, shape [n_samples, NP] + The matrix of features, where NP is the number of features + generated from applying the custom functions to the inputs. + + """ + + n_samples = X.shape[0] + + # preallocate matrix + XP = np.zeros((n_samples, self.n_output_features_)) + + current_feat = 0 + for lib in self.libraries_: + + # retrieve num features from lib + lib_n_output_features = lib.n_output_features_ + + start_feature_index = current_feat + end_feature_index = start_feature_index + lib_n_output_features + + XP[:, start_feature_index:end_feature_index] = lib.transform(X) + + current_feat += lib_n_output_features + + return XP + + def get_feature_names(self, input_features=None): + """Return feature names for output features. + + Parameters + ---------- + input_features : list of string, length n_features, optional + String names for input features if available. By default, + "x0", "x1", ... "xn_features" is used. + + Returns + ------- + output_feature_names : list of string, length n_output_features + """ + feature_names = list() + for lib in self.libraries_: + lib_feat_names = lib.get_feature_names(input_features) + feature_names += lib_feat_names + return feature_names diff --git a/test/feature_library/test_feature_library.py b/test/feature_library/test_feature_library.py index e3d5a3019..58e6f9f92 100644 --- a/test/feature_library/test_feature_library.py +++ b/test/feature_library/test_feature_library.py @@ -92,7 +92,7 @@ def test_change_in_data_shape(data_lorenz, library): [ (IdentityLibrary(), 3), (PolynomialLibrary(), 10), - (ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), 13), + (IdentityLibrary() + PolynomialLibrary(), 13), (FourierLibrary(), 6), (pytest.lazy_fixture("data_custom_library"), 9), ], @@ -111,7 +111,7 @@ def test_output_shape(data_lorenz, library, shape): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), - ConcatLibrary([PolynomialLibrary(), FourierLibrary()]), + PolynomialLibrary() + FourierLibrary(), pytest.lazy_fixture("data_custom_library"), ], ) @@ -184,5 +184,5 @@ def test_not_implemented(data_lorenz): def test_concat(): ident_lib = IdentityLibrary() poly_lib = PolynomialLibrary() - concat_lib = ConcatLibrary([ident_lib, poly_lib]) + concat_lib = ident_lib + poly_lib assert isinstance(concat_lib, ConcatLibrary) From 8a01a7b7548e22e91e34031aac7e485f11cef784 Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Thu, 30 Apr 2020 14:30:26 +0200 Subject: [PATCH 5/6] Reformat import order. --- pysindy/feature_library/__init__.py | 2 +- pysindy/feature_library/feature_library.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pysindy/feature_library/__init__.py b/pysindy/feature_library/__init__.py index 1a29f80a2..8548aae83 100644 --- a/pysindy/feature_library/__init__.py +++ b/pysindy/feature_library/__init__.py @@ -1,5 +1,5 @@ -from .feature_library import ConcatLibrary from .custom_library import CustomLibrary +from .feature_library import ConcatLibrary from .fourier_library import FourierLibrary from .identity_library import IdentityLibrary from .polynomial_library import PolynomialLibrary diff --git a/pysindy/feature_library/feature_library.py b/pysindy/feature_library/feature_library.py index 67e2f7b4a..18a63200b 100644 --- a/pysindy/feature_library/feature_library.py +++ b/pysindy/feature_library/feature_library.py @@ -3,11 +3,10 @@ """ import abc +import numpy as np from sklearn.base import TransformerMixin from sklearn.utils.validation import check_is_fitted -import numpy as np - class BaseFeatureLibrary(TransformerMixin): """ From 5d9cc8dc7ca6d7b5456fdf75bbfefa9ecf4dbf7d Mon Sep 17 00:00:00 2001 From: kopytjuk Date: Thu, 30 Apr 2020 14:37:14 +0200 Subject: [PATCH 6/6] Adapt all tests for plus operator. --- test/feature_library/test_feature_library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/feature_library/test_feature_library.py b/test/feature_library/test_feature_library.py index 58e6f9f92..04e3e4dd7 100644 --- a/test/feature_library/test_feature_library.py +++ b/test/feature_library/test_feature_library.py @@ -60,7 +60,7 @@ def test_bad_parameters(): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), - ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), + IdentityLibrary() + PolynomialLibrary(), pytest.lazy_fixture("data_custom_library"), ], ) @@ -76,7 +76,7 @@ def test_fit_transform(data_lorenz, library): IdentityLibrary(), PolynomialLibrary(), FourierLibrary(), - ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]), + IdentityLibrary() + PolynomialLibrary(), pytest.lazy_fixture("data_custom_library"), ], )