diff --git a/sysidentpy/basis_function/_basis_function.py b/sysidentpy/basis_function/_basis_function.py index 4d850f89..3563d988 100644 --- a/sysidentpy/basis_function/_basis_function.py +++ b/sysidentpy/basis_function/_basis_function.py @@ -1,5 +1,7 @@ """Basis Function for NARMAX models.""" +import warnings + from itertools import combinations_with_replacement from typing import Optional import numpy as np @@ -79,15 +81,19 @@ def fit( """ # 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)) + iterable_list = self.get_iterable_list(ylag, xlag, model_type) + combination_list = list( + combinations_with_replacement(iterable_list, self.degree) + ) if predefined_regressors is not None: - combinations = [combinations[index] for index in predefined_regressors] + combination_list = [ + combination_list[index] for index in predefined_regressors + ] psi = np.column_stack( [ - np.prod(data[:, combinations[i]], axis=1) - for i in range(len(combinations)) + np.prod(data[:, combination_list[i]], axis=1) + for i in range(len(combination_list)) ] ) psi = psi[max_lag:, :] @@ -97,8 +103,8 @@ def transform( self, data: np.ndarray, max_lag: int = 1, - ylag: int=1, - xlag: int=1, + ylag: int = 1, + xlag: int = 1, model_type: str = "NARMAX", predefined_regressors: Optional[np.ndarray] = None, ): @@ -202,7 +208,9 @@ def fit( """ # remove intercept (because the data always have the intercept) if self.degree > 1: - data = Polynomial().fit(data, max_lag, ylag, xlag, model_type, 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:] @@ -337,7 +345,9 @@ def fit( ): # remove intercept (because the data always have the intercept) if self.degree > 1: - data = Polynomial().fit(data, max_lag, ylag, xlag, model_type, 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:] @@ -381,6 +391,8 @@ def transform( 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. @@ -392,6 +404,7 @@ def transform( """ return self.fit(data, max_lag, ylag, xlag, model_type, predefined_regressors) + class Bilinear(BaseBasisFunction): r"""Build Bilinear basis function. @@ -403,11 +416,11 @@ class Bilinear(BaseBasisFunction): 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, + 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). @@ -427,7 +440,6 @@ class Bilinear(BaseBasisFunction): degree increases. High degrees can cause overfitting. """ - def __init__( self, degree: int = 2, @@ -472,26 +484,39 @@ def fit( """ # 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)) + iterable_list = self.get_iterable_list(ylag, xlag, model_type) + combination_list = 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.') + warnings.warn( + "You choose a bilinear basis function and nonlinear degree = 1." + "In this case, you have a linear polynomial model.", + stacklevel=2, + ) 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)) + 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)) + combination_list = list(set(combination_list) - set(combinations_xy)) if predefined_regressors is not None: - combinations = [combinations[index] for index in predefined_regressors] - + combination_list = [ + combination_list[index] for index in predefined_regressors + ] psi = np.column_stack( [ - np.prod(data[:, combinations[i]], axis=1) - for i in range(len(combinations)) + np.prod(data[:, combination_list[i]], axis=1) + for i in range(len(combination_list)) ] ) psi = psi[max_lag:, :] diff --git a/sysidentpy/basis_function/basis_function_base.py b/sysidentpy/basis_function/basis_function_base.py index 19a37cf4..476a402c 100644 --- a/sysidentpy/basis_function/basis_function_base.py +++ b/sysidentpy/basis_function/basis_function_base.py @@ -1,6 +1,6 @@ """Base class for Basis Function.""" -from itertools import combinations_with_replacement, chain +from itertools import chain from abc import ABCMeta, abstractmethod from typing import Optional @@ -21,6 +21,7 @@ def fit( max_lag: int = 1, ylag: int = 1, xlag: int = 1, + model_type: str = "NARMAX", predefined_regressors: Optional[np.ndarray] = None, ): """Abstract method.""" @@ -32,6 +33,7 @@ def transform( max_lag: int = 1, ylag: int = 1, xlag: int = 1, + model_type: str = "NARMAX", predefined_regressors: Optional[np.ndarray] = None, ) -> np.ndarray: """Abstract methods.""" @@ -71,10 +73,7 @@ def get_max_xlag(self, xlag: int = 1): return nx def get_iterable_list( - self, - ylag: int = 1, - xlag: int = 1, - model_type: str = "NARMAX" + self, ylag: int = 1, xlag: int = 1, model_type: str = "NARMAX" ): """Get iterable list. @@ -97,7 +96,6 @@ def get_iterable_list( ny = self.get_max_ylag(ylag) nx = self.get_max_xlag(xlag) iterable_list = list(range(ny + nx + 1)) - combinations = list(combinations_with_replacement(iterable_list, self.degree)) elif model_type == "NAR": ny = self.get_max_ylag(ylag) iterable_list = list(range(ny + 1)) @@ -105,4 +103,3 @@ def get_iterable_list( nx = self.get_max_xlag(xlag) iterable_list = list(range(nx + 1)) return iterable_list -