Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add concatenation support for multiple feature libraries #72

Merged
merged 6 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pysindy/feature_library/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .concat_library import ConcatLibrary
from .custom_library import CustomLibrary
from .fourier_library import FourierLibrary
from .identity_library import IdentityLibrary
Expand Down
121 changes: 121 additions & 0 deletions pysindy/feature_library/concat_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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
12 changes: 12 additions & 0 deletions test/feature_library/test_feature_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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
Expand Down Expand Up @@ -59,6 +60,7 @@ def test_bad_parameters():
IdentityLibrary(),
PolynomialLibrary(),
FourierLibrary(),
ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]),
pytest.lazy_fixture("data_custom_library"),
],
)
Expand All @@ -74,6 +76,7 @@ def test_fit_transform(data_lorenz, library):
IdentityLibrary(),
PolynomialLibrary(),
FourierLibrary(),
ConcatLibrary([IdentityLibrary(), PolynomialLibrary()]),
pytest.lazy_fixture("data_custom_library"),
],
)
Expand All @@ -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),
],
Expand All @@ -107,6 +111,7 @@ def test_output_shape(data_lorenz, library, shape):
IdentityLibrary(),
PolynomialLibrary(),
FourierLibrary(),
ConcatLibrary([PolynomialLibrary(), FourierLibrary()]),
pytest.lazy_fixture("data_custom_library"),
],
)
Expand Down Expand Up @@ -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)