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 bilinear basis function #138

Merged
merged 1 commit into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
214 changes: 208 additions & 6 deletions sysidentpy/basis_function/_basis_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def fit(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build the Polynomial information matrix.
Expand All @@ -59,6 +62,12 @@ def fit(
The lagged matrix built with respect to each lag and column.
max_lag : int
Target data used on training phase.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors : ndarray of int
The index of the selected regressors by the Model Structure
Selection algorithm.
Expand All @@ -70,7 +79,7 @@ def fit(

"""
# Create combinations of all columns based on its index
iterable_list = range(data.shape[1])
iterable_list = self.get_iterable_list(ylag, xlag, model_type)
combinations = list(combinations_with_replacement(iterable_list, self.degree))
if predefined_regressors is not None:
combinations = [combinations[index] for index in predefined_regressors]
Expand All @@ -88,6 +97,9 @@ def transform(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int=1,
xlag: int=1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build Polynomial Basis Functions.
Expand All @@ -98,6 +110,12 @@ def transform(
The lagged matrix built with respect to each lag and column.
max_lag : int
Maximum lag of list of regressors.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors: ndarray
Regressors to be filtered in the transformation.

Expand All @@ -107,7 +125,7 @@ def transform(
Transformed array.

"""
return self.fit(data, max_lag, predefined_regressors)
return self.fit(data, max_lag, ylag, xlag, model_type, predefined_regressors)


class Fourier(BaseBasisFunction):
Expand Down Expand Up @@ -149,6 +167,9 @@ def fit(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build the Polynomial information matrix.
Expand All @@ -163,6 +184,12 @@ def fit(
The lagged matrix built with respect to each lag and column.
max_lag : int
Target data used on training phase.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors : ndarray of int
The index of the selected regressors by the Model Structure
Selection algorithm.
Expand All @@ -175,7 +202,7 @@ def fit(
"""
# remove intercept (because the data always have the intercept)
if self.degree > 1:
data = Polynomial().fit(data, max_lag, predefined_regressors=None)
data = Polynomial().fit(data, max_lag, ylag, xlag, model_type, predefined_regressors=None)
data = data[:, 1:]
else:
data = data[max_lag:, 1:]
Expand Down Expand Up @@ -205,6 +232,9 @@ def transform(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build Fourier Basis Functions.
Expand All @@ -215,6 +245,12 @@ def transform(
The lagged matrix built with respect to each lag and column.
max_lag : int
Maximum lag of list of regressors.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors: ndarray
Regressors to be filtered in the transformation.

Expand All @@ -224,7 +260,7 @@ def transform(
Transformed array.

"""
return self.fit(data, max_lag, predefined_regressors)
return self.fit(data, max_lag, ylag, xlag, model_type, predefined_regressors)


class Bersntein(BaseBasisFunction):
Expand Down Expand Up @@ -294,11 +330,14 @@ def fit(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
# remove intercept (because the data always have the intercept)
if self.degree > 1:
data = Polynomial().fit(data, max_lag, predefined_regressors=None)
data = Polynomial().fit(data, max_lag, ylag, xlag, model_type, predefined_regressors=None)
data = data[:, 1:]
else:
data = data[max_lag:, 1:]
Expand All @@ -325,6 +364,169 @@ def transform(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
return self.fit(data, max_lag, predefined_regressors)
"""Build Bersntein Basis Functions.

Parameters
----------
data : ndarray of floats
The lagged matrix built with respect to each lag and column.
max_lag : int
Maximum lag of list of regressors.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
predefined_regressors: ndarray
Regressors to be filtered in the transformation.

Returns
-------
X_tr : {ndarray, sparse matrix} of shape (n_samples, n_features)
Transformed array.

"""
return self.fit(data, max_lag, ylag, xlag, model_type, predefined_regressors)

class Bilinear(BaseBasisFunction):
r"""Build Bilinear basis function.

A general bilinear input-output model takes the form

.. math::
y(k) = a_0 + \sum_{i=1}^{n_y} a_i y(k-i) + \sum_{i=1}^{n_u} b_i u(k-i) +
\sum_{i=1}^{n_y} \sum_{j=1}^{n_u} c_{ij} y(k-i) u(k-j)

This is a special case of the Polynomial NARMAX model.

Bilinear system theory has been widely studied and it plays an important role in the context of continuous-time
systems. This is because, roughly speaking, the set of bilinear
systems is dense in the space of continuous-time systems and any continuous causal
functional can be arbitrarily well approximated by bilinear systems within any
bounded time interval (see for example Fliess and Normand-Cyrot 1982). Moreover,
many real continuous-time processes are naturally in bilinear form. A few examples
are distillation columns (España and Landau 1978), nuclear and thermal control
processes (Mohler 1973).

Sampling the continuous-time bilinear system, however, produces a NARMAX model
which is more complex than a discrete-time bilinear model.

Parameters
----------
degree : int (max_degree), default=2
The maximum degree of the polynomial features.

Notes
-----
Be aware that the number of features in the output array scales
significantly as the number of inputs, the max lag of the input and output, and
degree increases. High degrees can cause overfitting.
"""


def __init__(
self,
degree: int = 2,
):
self.degree = degree

def fit(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build the Bilinear information matrix.

Each columns of the information matrix represents a candidate
regressor. The set of candidate regressors are based on xlag,
ylag, and degree defined by the user.

Parameters
----------
data : ndarray of floats
The lagged matrix built with respect to each lag and column.
max_lag : int
Target data used on training phase.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors : ndarray of int
The index of the selected regressors by the Model Structure
Selection algorithm.

Returns
-------
psi = ndarray of floats
The lagged matrix built in respect with each lag and column.

"""
# Create combinations of all columns based on its index
iterable_list = self.get_iterable_list(ylag, xlag, model_type)
combinations = list(combinations_with_replacement(iterable_list, self.degree))
if self.degree == 1:
Warning('You choose a bilinear basis function and nonlinear degree = 1. In this case, you have a linear polynomial model.')
else:
ny = self.get_max_ylag(ylag)
nx = self.get_max_xlag(xlag)
combination_ylag = list(combinations_with_replacement(list(range(1, ny + 1)), self.degree))
combination_xlag = list(combinations_with_replacement(list(range(ny + 1, nx + ny + 1)), self.degree))
combinations_xy = combination_xlag + combination_ylag
combinations = list(set(combinations)-set(combinations_xy))

if predefined_regressors is not None:
combinations = [combinations[index] for index in predefined_regressors]


psi = np.column_stack(
[
np.prod(data[:, combinations[i]], axis=1)
for i in range(len(combinations))
]
)
psi = psi[max_lag:, :]
return psi

def transform(
self,
data: np.ndarray,
max_lag: int = 1,
ylag: int = 1,
xlag: int = 1,
model_type: str = "NARMAX",
predefined_regressors: Optional[np.ndarray] = None,
):
"""Build Polynomial Basis Functions.

Parameters
----------
data : ndarray of floats
The lagged matrix built with respect to each lag and column.
max_lag : int
Maximum lag of list of regressors.
ylag : ndarray of int
The range of lags according to user definition.
xlag : ndarray of int
The range of lags according to user definition.
model_type : str
The type of the model (NARMAX, NAR or NFIR).
predefined_regressors: ndarray
Regressors to be filtered in the transformation.

Returns
-------
X_tr : {ndarray, sparse matrix} of shape (n_samples, n_features)
Transformed array.

"""
return self.fit(data, max_lag, ylag, xlag, model_type, predefined_regressors)
Loading
Loading