Exemple #1
0
class EstimatorQuadraticB(EstimatorCubic):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """

    ## Cubic model:
    b44_quadratic_equation = sp.Eq(
        B_44, B_1 * phi_dot + B_2 * phi_dot * sp.Abs(phi_dot))
    restoring_equation_quadratic = sp.Eq(C_44, C_1 * phi)

    subs = [(B_44, sp.solve(b44_quadratic_equation, B_44)[0]),
            (C_44, sp.solve(restoring_equation_quadratic, C_44)[0])]
    roll_decay_equation = equations.roll_decay_equation_general_himeno.subs(
        subs)
    # Normalizing with A_44:
    lhs = (roll_decay_equation.lhs / A_44).subs(
        equations.subs_normalize).simplify()
    roll_decay_equation_A = sp.Eq(lhs=lhs, rhs=0)

    acceleration = sp.solve(roll_decay_equation_A, phi_dot_dot)[0]
    functions = dict(EstimatorCubic.functions)
    functions['acceleration'] = lambdify(acceleration)

    @classmethod
    def load(cls, B_1A: float, B_2A: float, C_1A: float, X=None, **kwargs):
        """
        Load data and parameters from an existing fitted estimator

        A_44 is total roll intertia [kg*m**2] (including added mass)

        Parameters
        ----------
        B_1A
            B_1/A_44 : linear damping
        B_2A
            B_2/A_44 : quadratic damping
        C_1A
            C_1/A_44 : linear stiffness

        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        data = {
            'B_1A': B_1A,
            'B_2A': B_2A,
            'C_1A': C_1A,
        }

        return super(cls, cls)._load(data=data, X=X)
Exemple #2
0
class EstimatorQuadratic(EstimatorCubic):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """

    ## Quadratic model with Cubic restoring force:
    b44_quadratic_equation = sp.Eq(
        B_44, B_1 * phi_dot + B_2 * phi_dot * sp.Abs(phi_dot))
    restoring_equation_cubic = sp.Eq(C_44,
                                     C_1 * phi + C_3 * phi**3 + C_5 * phi**5)

    subs = [(B_44, sp.solve(b44_quadratic_equation, B_44)[0]),
            (C_44, sp.solve(restoring_equation_cubic, C_44)[0])]
    roll_decay_equation = equations.roll_decay_equation_general_himeno.subs(
        subs)
    # Normalizing with A_44:
    lhs = (roll_decay_equation.lhs / A_44).subs(
        equations.subs_normalize).simplify()
    roll_decay_equation_A = sp.Eq(lhs=lhs, rhs=0)

    acceleration = sp.solve(roll_decay_equation_A, phi_dot_dot)[0]
    functions = dict(EstimatorCubic.functions)
    functions['acceleration'] = lambdify(acceleration)
    def load(cls, file_path: str):
        with open(file_path, 'rb') as file:
            polynom = dill.load(file)

        polynom.equation = polynom.get_equation()
        polynom.lamda = lambdify(polynom.equation.rhs)

        return polynom
Exemple #4
0
class DirectLinearEstimator(DirectEstimator):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """
    # Defining the diff equation for this estimator:
    rhs = -phi_dot_dot / (omega0**2) - 2 * zeta / omega0 * phi_dot
    roll_diff_equation = sp.Eq(lhs=phi, rhs=rhs)
    acceleration = sp.Eq(lhs=phi,
                         rhs=sp.solve(roll_diff_equation,
                                      phi.diff().diff())[0])
    functions = {'acceleration': lambdify(acceleration.rhs)}

    @classmethod
    def load(cls, omega0: float, zeta: float, X=None):
        """
        Load data and parameters from an existing fitted estimator

        A_44 is total roll intertia [kg*m**2] (including added mass)

        Parameters
        ----------
        omega0:
            roll natural frequency[rad/s]
        zeta:
            linear roll damping [-]

        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        data = {
            'omega0': omega0,
            'zeta': zeta,
        }

        return super(cls, cls)._load(data=data, X=X)
import numpy as np
import pandas as pd
from scipy.integrate import odeint
from rolldecayestimators import DirectEstimator
from rolldecayestimators.symbols import *
from rolldecayestimators.substitute_dynamic_symbols import lambdify

dGM = sp.symbols('dGM')
lhs = phi_dot_dot + 2*zeta*omega0*phi_dot + omega0**2*phi+(dGM*phi*sp.Abs(phi)) + d*sp.Abs(phi_dot)*phi_dot
roll_diff_equation = sp.Eq(lhs=lhs, rhs=0)
acceleration = sp.Eq(lhs=phi, rhs=sp.solve(roll_diff_equation, phi.diff().diff())[0])
calculate_acceleration = lambdify(acceleration.rhs)

# Defining the diff equation for this estimator:

class DirectEstimatorImproved(DirectEstimator):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """

    @staticmethod
    def estimator(df, omega0, zeta, dGM, d):
        phi = df['phi']
        phi1d = df['phi1d']
Exemple #6
0
class EstimatorCubic(DirectEstimator):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """

    ## Cubic model:
    b44_cubic_equation = sp.Eq(
        B_44,
        B_1 * phi_dot + B_2 * phi_dot * sp.Abs(phi_dot) + B_3 * phi_dot**3)
    restoring_equation_cubic = sp.Eq(C_44,
                                     C_1 * phi + C_3 * phi**3 + C_5 * phi**5)

    subs = [(B_44, sp.solve(b44_cubic_equation, B_44)[0]),
            (C_44, sp.solve(restoring_equation_cubic, C_44)[0])]
    roll_decay_equation = equations.roll_decay_equation_general_himeno.subs(
        subs)
    # Normalizing with A_44:
    lhs = (roll_decay_equation.lhs / A_44).subs(
        equations.subs_normalize).simplify()
    roll_decay_equation_A = sp.Eq(lhs=lhs, rhs=0)

    acceleration = sp.solve(roll_decay_equation_A, phi_dot_dot)[0]
    functions = {'acceleration': lambdify(acceleration)}

    C_1_equation = equations.C_equation_linear.subs(symbols.C,
                                                    symbols.C_1)  # C_1 = GM*gm

    eqs = [C_1_equation, equations.normalize_equations[symbols.C_1]]

    A44_equation = sp.Eq(
        symbols.A_44,
        sp.solve(eqs, symbols.C_1, symbols.A_44)[symbols.A_44])
    functions['A44'] = lambdify(sp.solve(A44_equation, symbols.A_44)[0])

    eqs = [
        equations.C_equation_linear,
        equations.omega0_equation,
        A44_equation,
    ]
    omgea0_equation = sp.Eq(
        symbols.omega0,
        sp.solve(eqs, symbols.A_44, symbols.C, symbols.omega0)[0][2])
    functions['omega0'] = lambdify(
        sp.solve(omgea0_equation, symbols.omega0)[0])

    def __init__(self,
                 maxfev=1000,
                 bounds={},
                 ftol=10**-15,
                 p0={},
                 fit_method='integration'):

        new_bounds = {
            'B_1A': (0, np.inf),  # Assuming only positive coefficients
            #    'B_2A': (0, np.inf),  # Assuming only positive coefficients
            #    'B_3A': (0, np.inf),  # Assuming only positive coefficients
        }
        new_bounds.update(bounds)
        bounds = new_bounds

        super().__init__(maxfev=maxfev,
                         bounds=bounds,
                         ftol=ftol,
                         p0=p0,
                         fit_method=fit_method,
                         omega_regression=True)

    @classmethod
    def load(cls,
             B_1A: float,
             B_2A: float,
             B_3A: float,
             C_1A: float,
             C_3A: float,
             C_5A: float,
             X=None,
             **kwargs):
        """
        Load data and parameters from an existing fitted estimator

        A_44 is total roll intertia [kg*m**2] (including added mass)

        Parameters
        ----------
        B_1A
            B_1/A_44 : linear damping
        B_2A
            B_2/A_44 : quadratic damping
        B_3A
            B_3/A_44 : cubic damping
        C_1A
            C_1/A_44 : linear stiffness
        C_3A
            C_3/A_44 : cubic stiffness
        C_5A
            C_5/A_44 : pentatonic stiffness

        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        data = {
            'B_1A': B_1A,
            'B_2A': B_2A,
            'B_3A': B_3A,
            'C_1A': C_1A,
            'C_3A': C_3A,
            'C_5A': C_5A,
        }

        return super(cls, cls)._load(data=data, X=X)

    def calculate_additional_parameters(self, A44):
        check_is_fitted(self, 'is_fitted_')

        parameters_additional = {}

        for key, value in self.parameters.items():
            symbol_key = sp.Symbol(key)
            new_key = key[0:-1]
            symbol_new_key = ss.Symbol(new_key)

            if symbol_new_key in equations.normalize_equations:
                normalize_equation = equations.normalize_equations[
                    symbol_new_key]
                solution = sp.solve(normalize_equation, symbol_new_key)[0]
                new_value = solution.subs([
                    (symbol_key, value),
                    (symbols.A_44, A44),
                ])

                parameters_additional[new_key] = new_value

        return parameters_additional

    def result_for_database(self, meta_data={}):
        s = super().result_for_database(meta_data=meta_data)

        inputs = pd.Series(meta_data)

        inputs['m'] = inputs['Volume'] * inputs['rho']
        parameters = pd.Series(self.parameters)
        inputs = parameters.combine_first(inputs)

        s['A_44'] = run(self.functions['A44'], inputs=inputs)
        parameters_additional = self.calculate_additional_parameters(
            A44=s['A_44'])
        s.update(parameters_additional)

        inputs['A_44'] = s['A_44']
        s['omega0'] = run(function=self.functions['omega0'], inputs=inputs)

        self.results = s  # Store it also

        return s
Exemple #7
0
class RollDecay(BaseEstimator):

    # Defining the diff equation for this estimator:
    rhs = -phi_dot_dot / (omega0**2) - 2 * zeta / omega0 * phi_dot
    roll_diff_equation = sp.Eq(lhs=phi, rhs=rhs)
    acceleration = sp.Eq(lhs=phi,
                         rhs=sp.solve(roll_diff_equation,
                                      phi.diff().diff())[0])
    functions = {'acceleration': lambdify(acceleration.rhs)}

    def __init__(self,
                 ftol=1e-09,
                 maxfev=100000,
                 bounds={},
                 p0={},
                 fit_method='derivation',
                 omega_regression=True,
                 omega0=None):
        self.is_fitted_ = False

        self.phi_key = 'phi'  # Roll angle [rad]
        self.phi1d_key = 'phi1d'  # Roll velocity [rad/s]
        self.phi2d_key = 'phi2d'  # Roll acceleration [rad/s2]
        self.y_key = self.phi2d_key
        self.boundaries = bounds
        self.p0 = p0
        self.maxfev = maxfev
        self.ftol = ftol
        self.set_fit_method(fit_method=fit_method)
        self.omega_regression = omega_regression
        self.assert_success = True
        self._omega0 = omega0

    @classmethod
    def load(cls, data: {}, X=None):
        """
        Load data and parameters from an existing fitted estimator

        Parameters
        ----------
        data : dict
            Dict containing data for this estimator such as parameters
        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        return cls._load(data=data, X=X)

    @classmethod
    def _load(cls, data: {}, X=None):
        """
        Load data and parameters from an existing fitted estimator

        Parameters
        ----------
        data : dict
            Dict containing data for this estimator such as parameters
        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        estimator = cls()
        estimator.load_data(data=data)
        estimator.load_X(X=X)
        return estimator

    def load_data(self, data: {}):
        parameter_names = self.parameter_names
        missing = list(set(parameter_names) - set(data.keys()))
        if len(missing) > 0:
            raise ValueError(
                'The following parameters are missing in data:%s' % missing)

        parameters = {
            key: value
            for key, value in data.items() if key in parameter_names
        }
        self.parameters = parameters
        self.is_fitted_ = True

    def load_X(self, X=None):
        if isinstance(X, pd.DataFrame):
            self.X = X

    def set_fit_method(self, fit_method):
        self.fit_method = fit_method

        if self.fit_method == 'derivation':
            self.y_key = self.phi2d_key
        elif self.fit_method == 'integration':
            self.y_key = self.phi_key
        else:
            raise ValueError('Unknown fit_mehod:%s' % self.fit_method)

    def __repr__(self):
        if self.is_fitted_:
            parameters = ''.join(
                '%s:%0.3f, ' % (key, value)
                for key, value in sorted(self.parameters.items()))[0:-1]
            return '%s(%s)' % (self.__class__.__name__, parameters)
        else:
            return '%s' % (self.__class__.__name__)

    @property
    def calculate_acceleration(self):
        return self.functions['acceleration']

    @property
    def parameter_names(self):
        signature = inspect.signature(self.calculate_acceleration)

        remove = [self.phi_key, self.phi1d_key]
        if not self.omega_regression:
            remove.append('omega0')

        return list(set(signature.parameters.keys()) - set(remove))

    @staticmethod
    def error(x, self, xs, ys):
        #return np.sum((ys - self.estimator(x, xs))**2)
        return ys - self.estimator(x, xs)

    def estimator(self, x, xs):
        self.parameters = {key: x for key, x in zip(self.parameter_names, x)}

        if not self.omega_regression:
            self.parameters['omega0'] = self.omega0

        if self.fit_method == 'derivation':
            self.parameters['phi'] = xs[self.phi_key]
            self.parameters['phi1d'] = xs[self.phi1d_key]
            return self.estimator_acceleration(parameters=self.parameters)
        elif self.fit_method == 'integration':
            t = xs.index

            phi0 = xs.iloc[0][self.phi_key]

            try:
                phi1d0 = xs.iloc[0][self.phi1d_key]
            except:
                phi1d0 = 0

            return self.estimator_integration(t=t, phi0=phi0, phi1d0=phi1d0)
        else:
            raise ValueError('Unknown fit_mehod:%s' % self.fit_method)

    def estimator_acceleration(self, parameters):
        acceleration = self.calculate_acceleration(**parameters)
        return acceleration

    def estimator_integration(self, t, phi0, phi1d0):

        try:
            df = self.simulate(t=t, phi0=phi0, phi1d0=phi1d0)
        except:
            df = pd.DataFrame(index=t)
            df['phi'] = np.inf
            df['phi1d'] = np.inf

        return df[self.y_key]

    def fit(self, X, y=None, **kwargs):
        self.X = X.copy()

        kwargs = {'self': self, 'xs': X, 'ys': X[self.y_key]}

        if self.fit_method == 'integration':
            self.result = least_squares(fun=self.error,
                                        x0=self.initial_guess,
                                        kwargs=kwargs,
                                        bounds=self.bounds,
                                        ftol=self.ftol,
                                        max_nfev=self.maxfev,
                                        loss='soft_l1',
                                        f_scale=0.1)
        else:
            self.result = least_squares(fun=self.error,
                                        x0=self.initial_guess,
                                        kwargs=kwargs,
                                        ftol=self.ftol,
                                        max_nfev=self.maxfev,
                                        method='lm')

        if self.assert_success:
            if not self.result['success']:
                raise FitError(self.result['message'])

        self.parameters = {
            key: x
            for key, x in zip(self.parameter_names, self.result.x)
        }

        if not self.omega_regression:
            self.parameters['omega0'] = self.omega0

        self.is_fitted_ = True

    def simulate(self, t, phi0, phi1d0) -> pd.DataFrame:

        states0 = [phi0, phi1d0]

        #states = odeint(self.roll_decay_time_step, y0=states0, t=t, args=(self,parameters))
        #df[self.phi_key] = states[:, 0]
        #df[self.phi1d_key] = states[:, 1]

        t_ = t - t[0]
        t_span = [t_[0], t_[-1]]
        self.simulation_result = solve_ivp(
            fun=self.roll_decay_time_step,
            t_span=t_span,
            y0=states0,
            t_eval=t_,
        )
        if not self.simulation_result['success']:
            raise ValueError('Simulation failed')

        df = pd.DataFrame(index=t)
        df[self.phi_key] = self.simulation_result.y[0, :]
        df[self.phi1d_key] = self.simulation_result.y[1, :]
        p_old = df[self.phi1d_key]
        phi_old = df[self.phi_key]
        df[self.phi2d_key] = self.calculate_acceleration(phi1d=p_old,
                                                         phi=phi_old,
                                                         **self.parameters)

        return df

    def roll_decay_time_step(self, t, states):
        # states:
        # [phi,phi1d]

        phi_old = states[0]
        p_old = states[1]

        phi1d = p_old
        calculate_acceleration = self.calculate_acceleration
        phi2d = calculate_acceleration(phi1d=p_old,
                                       phi=phi_old,
                                       **self.parameters)

        d_states_dt = np.array([phi1d, phi2d])

        return d_states_dt

    def predict(self, X) -> pd.DataFrame:

        check_is_fitted(self, 'is_fitted_')

        phi0 = X[self.phi_key].iloc[0]
        phi1d0 = X[self.phi1d_key].iloc[0]
        t = np.array(X.index)
        return self.simulate(t=t, phi0=phi0, phi1d0=phi1d0)

    def score(self, X=None, y=None, sample_weight=None):
        """
        Return the coefficient of determination R_b^2 of the prediction.

        The coefficient R_b^2 is defined as (1 - u/v), where u is the residual sum of squares
        ((y_true - y_pred) ** 2).sum() and v is the total sum of squares ((y_true - y_true.mean()) ** 2).sum().
        The best possible score is 1.0 and it can be negative (because the model can be arbitrarily worse).
        A constant model that always predicts the expected value of y, disregarding the input features,
        would get a R_b^2 score of 0.0.

        Parameters
        ----------
        X : array-like of shape (n_samples, n_features)
            Test samples. For some estimators this may be a precomputed kernel matrix or a list of generic
            objects instead, shape = (n_samples, n_samples_fitted), where n_samples_fitted is the number of samples
            used in the fitting for the estimator.

        y : Dummy not used

        sample_weight : Dummy

        Returns
        -------
        score : float
            R_b^2 of self.predict(X) wrt. y.

        """

        y_true, y_pred = self.true_and_prediction(X=X)

        return r2_score(y_true=y_true, y_pred=y_pred)

    def true_and_prediction(self, X=None):

        if X is None:
            X = self.X

        y_true = X[self.phi_key]
        df_sim = self.predict(X)
        y_pred = df_sim[self.phi_key]
        return y_true, y_pred

    @property
    def bounds(self):

        minimums = []
        maximums = []

        for key in self.parameter_names:

            boundaries = self.boundaries.get(key, (-np.inf, np.inf))
            assert len(boundaries) == 2
            minimums.append(boundaries[0])
            maximums.append(boundaries[1])

        return [tuple(minimums), tuple(maximums)]

    @property
    def initial_guess(self):
        p0 = []
        for key in self.parameter_names:
            p0.append(self.p0.get(key, 0.5))

        return p0

    @property
    def omega0(self):
        """
        Mean natural frequency
        Returns
        -------

        """

        if not self._omega0 is None:
            return self._omega0

        frequencies, dft = fft(self.X['phi'])
        omega0 = fft_omega0(frequencies=frequencies, dft=dft)

        return omega0

    def result_for_database(self, meta_data={}, score=True):
        check_is_fitted(self, 'is_fitted_')

        s = {}
        s.update(self.parameters)
        if score:
            s['score'] = self.score(X=self.X)

        if not self.X is None:
            s['phi_start'] = self.X.iloc[0]['phi']
            s['phi_stop'] = self.X.iloc[-1]['phi']

        if hasattr(self, 'omega0'):
            s['omega0_fft'] = self.omega0

        self.results = s  # Store it also

        return s
import sympy as sp
from rolldecayestimators.substitute_dynamic_symbols import lambdify
from rolldecayestimators import equations
from rolldecayestimators import symbols

B44_lambda = lambdify(
    sp.solve(equations.B44_hat_equation_quadratic, symbols.B_44_hat)[0])
B_1_hat_lambda = lambdify(
    sp.solve(equations.B_1_hat_equation, symbols.B_1_hat)[0])
B_e_hat_lambda = lambdify(
    sp.solve(equations.B_e_hat_equation, symbols.B_e_hat)[0])
B_2_hat_lambda = lambdify(
    sp.solve(equations.B_2_hat_equation, symbols.B_2_hat)[0])

omega0_lambda = lambdify(
    sp.solve(equations.omega0_hat_equation, symbols.omega_hat)[0])

omega = omega_from_hat = lambdify(
    sp.solve(equations.omega0_hat_equation, symbols.omega0)[0])
omega_hat = omega_to_hat = lambdify(
    sp.solve(equations.omega0_hat_equation, symbols.omega_hat)[0])

B_e_lambda = lambdify(sp.solve(equations.B_e_equation, symbols.B_e)[0])
B_e_lambda_cubic = lambdify(
    sp.solve(equations.B_e_equation_cubic, symbols.B_e)[0])

B44_hat_equation = equations.B44_hat_equation.subs(symbols.B_44, symbols.B)
B_hat_lambda = lambdify(sp.solve(B44_hat_equation, symbols.B_44_hat)[0])
B_to_hat_lambda = lambdify(sp.solve(B44_hat_equation, symbols.B_44_hat)[0])
B_from_hat_lambda = lambdify(sp.solve(B44_hat_equation, symbols.B)[0])
import numpy as np
import sympy as sp
import pandas as pd

from rolldecayestimators import equations
from rolldecayestimators import symbols
from rolldecayestimators import lambdas

from rolldecayestimators.substitute_dynamic_symbols import lambdify

lambda_B_1_zeta = lambdify(sp.solve(equations.B_1_zeta_eq, symbols.B_1)[0])

eq_phi1d = sp.Eq(
    symbols.phi_dot_dot,
    sp.solve(equations.roll_decay_equation_cubic_A, symbols.phi_dot_dot)[0])

accelaration_lambda = lambdify(sp.solve(eq_phi1d, symbols.phi_dot_dot)[0])


def find_peaks(df_state_space):
    df_state_space['phi_deg'] = np.rad2deg(df_state_space['phi'])

    mask = (np.sign(df_state_space['phi1d']) != np.sign(
        np.roll(df_state_space['phi1d'], -1)))
    mask[0] = False
    mask[-1] = False

    df_max = df_state_space.loc[mask].copy()
    df_max['id'] = np.arange(len(df_max)) + 1
    return df_max
class AnalyticalLinearEstimator(DirectEstimator):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    demo_param : str, default='demo_param'
        A parameter used for demonstation of how to pass and store paramters.
    """

    # Defining the diff equation for this estimator:
    rhs = -phi_dot_dot / (omega0**2) - 2 * zeta / omega0 * phi_dot
    roll_diff_equation = sp.Eq(lhs=phi, rhs=rhs)
    acceleration = sp.Eq(lhs=phi,
                         rhs=sp.solve(roll_diff_equation,
                                      phi.diff().diff())[0])
    functions = {
        'phi':
        lambdify(sp.solve(equations.analytical_solution, phi)[0]),
        'velocity':
        lambdify(sp.solve(equations.analytical_phi1d, phi_dot)[0]),
        'acceleration':
        lambdify(sp.solve(equations.analytical_phi2d, phi_dot_dot)[0]),
    }

    @property
    def parameter_names(self):
        signature = inspect.signature(self.calculate_acceleration)

        remove = ['phi_0', 'phi_01d', 't']
        if not self.omega_regression:
            remove.append('omega0')

        return list(set(signature.parameters.keys()) - set(remove))

    def estimator(self, x, xs):
        parameters = {key: x for key, x in zip(self.parameter_names, x)}

        if not self.omega_regression:
            parameters['omega0'] = self.omega0

        t = xs.index
        phi_0 = xs.iloc[0][self.phi_key]
        phi_01d = xs.iloc[0][self.phi1d_key]

        return self.functions['phi'](t=t,
                                     phi_0=phi_0,
                                     phi_01d=phi_01d,
                                     **parameters)

    def predict(self, X) -> pd.DataFrame:

        check_is_fitted(self, 'is_fitted_')

        t = X.index
        phi_0 = X.iloc[0][self.phi_key]
        phi_01d = X.iloc[0][self.phi1d_key]

        df = pd.DataFrame(index=t)
        df['phi'] = self.functions['phi'](t=t,
                                          phi_0=phi_0,
                                          phi_01d=phi_01d,
                                          **self.parameters)
        df['phi1d'] = self.functions['velocity'](t=t,
                                                 phi_0=phi_0,
                                                 phi_01d=phi_01d,
                                                 **self.parameters)
        df['phi2d'] = self.functions['acceleration'](t=t,
                                                     phi_0=phi_0,
                                                     phi_01d=phi_01d,
                                                     **self.parameters)

        return df
Exemple #11
0
class IkedaQuadraticEstimator(IkedaEstimator):

    functions_ikeda = IkedaEstimator.functions_ikeda
    functions_ikeda.append(
        lambdify(sp.solve(equations.B_e_equation, symbols.B_e)[0]))  # 2
    functions_ikeda.append(
        lambdify(sp.solve(equations.zeta_B1_equation, symbols.zeta)[0]))  # 3
    functions_ikeda.append(
        lambdify(sp.solve(equations.d_B2_equation, symbols.d)[0]))  # 4

    @property
    def B_e_lambda(self):
        return self.functions_ikeda[2]

    @property
    def zeta_B1_lambda(self):
        return self.functions_ikeda[3]

    @property
    def d_B2_lambda(self):
        return self.functions_ikeda[4]

    def fit(self, X=None, y=None, **kwargs):
        self.X = X

        if not self.X is None:
            self.phi_max = np.rad2deg(self.X[
                self.phi_key].abs().max())  ## Initial roll angle in [deg]

        DRAFT = (self.TA + self.TF) / 2
        omega0 = self.omega0

        if (self.lpp * self.beam * DRAFT > 0):
            CB = self.Volume / (self.lpp * self.beam * DRAFT)
        else:
            raise IkedaEstimatorFitError('lpp, b or DRAFT is zero or nan!')

        self.ikeda_parameters = {
            'LPP': self.lpp,
            'Beam': self.beam,
            'DRAFT': DRAFT,
            'PHI': self.phi_max,
            'BKL': self.BKL,
            'BKB': self.BKB,
            'OMEGA': omega0,
            'OG': (-self.kg + DRAFT),
            'CB': CB,
            'V': self.V,
            'CMID': self.A0,
            'verify_input': self.verify_input,
            'limit_inputs': self.limit_inputs,
        }

        #self.result=self.calculate(**kwargs)
        self.result = {}

        self.result_variation = self.calculate_phi_a_variation()

        if self.two_point_regression:
            B_1_, B_2_ = self.calculate_two_point_regression(**kwargs)
            self.result['B_1'] = B_1 = B_1_['B_44']
            self.result['B_2'] = B_2 = B_2_['B_44']

            B_1_, B_2_ = self.fit_Bs()  # Not used...
        else:
            B_1, B_2 = self.fit_Bs()
            self.result['B_1'] = B_1
            self.result['B_2'] = B_2

        zeta, d = self.Bs_to_zeta_d(B_1=B_1, B_2=B_2)
        factor = 1.0  # Factor
        phi_a = np.abs(np.deg2rad(self.phi_max)) / factor  # [Radians]
        self.result['B_e'] = self.B_e_lambda(
            B_1=B_1,
            B_2=B_2,
            omega0=self.ikeda_parameters['OMEGA'],
            phi_a=phi_a)

        self.parameters = {
            'zeta': zeta,
            'd': d,
            'omega0': omega0,
        }

        self.is_fitted_ = True

    def calculate_two_point_regression(self, **kwargs):
        # Two point regression:
        data = {
            'lpp': self.lpp,
            'b': self.beam,
            'DRAFT': (self.TA + self.TF) / 2,
            'phi_max': self.phi_max,
            'BKL': self.BKL,
            'BKB': self.BKB,
            'omega0': self.ikeda_parameters['OMEGA'],
            'kg': self.kg,
            'CB': self.ikeda_parameters['CB'],
            'A0': self.A0,
            'V': self.V,
            'Volume': self.Volume,
        }
        row1 = pd.Series(data)
        row1.phi_max *= 0.5
        row2 = pd.Series(data)
        s1_hat = calculate(row1,
                           verify_input=self.verify_input,
                           limit_inputs=self.limit_inputs,
                           **kwargs)
        s2_hat = calculate(row2,
                           verify_input=self.verify_input,
                           limit_inputs=self.limit_inputs,
                           **kwargs)

        s1_hat = self.B44_lambda(B_44_hat=s1_hat,
                                 Disp=row1.Volume,
                                 b=row1.b,
                                 g=self.g,
                                 rho=self.rho)
        s2_hat = self.B44_lambda(B_44_hat=s2_hat,
                                 Disp=row2.Volume,
                                 b=row2.b,
                                 g=self.g,
                                 rho=self.rho)

        s1 = pd.Series()
        s2 = pd.Series()
        for key, value in s1_hat.items():
            new_key = key.replace('_hat', '')
            s1[new_key] = s1_hat[key]
            s2[new_key] = s2_hat[key]

        x = np.deg2rad([row1.phi_max, row2.phi_max
                        ]) * 8 * row1.omega0 / (3 * np.pi)
        B_2 = (s2 - s1) / (x[1] - x[0])
        B_1 = s1 - B_2 * x[0]

        # Save all of the component as one linear term: _1 and a quadratic term: _2
        for key in s1.index:
            new_name_1 = '%s_1' % key
            self.result[new_name_1] = s1[key]

            new_name_2 = '%s_2' % key
            self.result[new_name_2] = s2[key]

        return B_1, B_2

    def calculate_phi_a_variation(self):

        data = {
            'lpp': self.lpp,
            'b': self.beam,
            'DRAFT': (self.TA + self.TF) / 2,
            'phi_max': self.phi_max,
            'BKL': self.BKL,
            'BKB': self.BKB,
            'omega0': self.ikeda_parameters['OMEGA'],
            'kg': self.kg,
            'CB': self.ikeda_parameters['CB'],
            'A0': self.A0,
            'V': self.V,
            'Volume': self.Volume,
        }
        self.ship = ship = pd.Series(data)

        N = 40
        changes = np.linspace(1, 0.0001, N)
        df_variation = variate_ship(ship=ship, key='phi_max', changes=changes)
        result = calculate_variation(df=df_variation,
                                     limit_inputs=self.limit_inputs,
                                     verify_input=self.verify_input)
        df_variation['g'] = 9.81
        df_variation['rho'] = 1000
        result = pd.concat((result, df_variation), axis=1)

        result['B_44'] = self.B44_lambda(B_44_hat=result.B_44_hat,
                                         Disp=ship.Volume,
                                         b=ship.b,
                                         g=result.g,
                                         rho=result.rho)
        result.dropna(inplace=True)
        return result

    def fit_Bs(self):
        def fit(df, B_1, B_2):
            omega0 = df['omega0']
            phi_a = np.deg2rad(
                df['phi_max']
            )  # Deg or rad (Radians gave better results actually)???
            #phi_a = df['phi_max']  # Deg or rad???
            return self.B_e_lambda(B_1, B_2, omega0, phi_a)

        coeffs, _ = curve_fit(f=fit,
                              xdata=self.result_variation,
                              ydata=self.result_variation['B_44'])
        B_1 = coeffs[0]
        B_2 = coeffs[1]
        self.result_variation['B_44_fit'] = fit(self.result_variation, *coeffs)
        return B_1, B_2

    def Bs_to_zeta_d(self, B_1, B_2):
        m = self.Volume * self.rho
        zeta = self.zeta_B1_lambda(B_1=B_1,
                                   GM=self.gm,
                                   g=self.g,
                                   m=m,
                                   omega0=self.ikeda_parameters['OMEGA'])
        d = self.d_B2_lambda(B_2=B_2,
                             GM=self.gm,
                             g=self.g,
                             m=m,
                             omega0=self.ikeda_parameters['OMEGA'])
        return zeta, d

    def plot_variation(self, ax=None):

        if ax is None:
            fig, ax = plt.subplots()

        self.result_variation.plot(y=['B_44_hat'], ax=ax)

    def plot_B_fit(self, ax=None):

        if ax is None:
            fig, ax = plt.subplots()

        self.result_variation.plot(y='B_44', ax=ax)
        self.result_variation.plot(y='B_44_fit', ax=ax, style='--')

    @classmethod
    def load(cls, data: {}, X=None):
        """
        Load data and parameters from an existing fitted estimator

        Parameters
        ----------
        data : dict
            Dict containing data for this estimator such as parameters
        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        estimator = cls(**data)
        estimator.load_data(data=data)
        estimator.load_X(X=X)
        return estimator
Exemple #12
0
class IkedaEstimator(DirectEstimator):

    eqs = [
        equations.zeta_equation,  # 0
        equations.omega0_equation_linear
    ]  # 1
    functions_ikeda = [
        lambdify(sp.solve(eqs, symbols.A_44, symbols.zeta)[0][1]),
        lambdify(sp.solve(equations.B44_equation, symbols.B_44)[0]),
    ]

    def __init__(self,
                 lpp: float,
                 TA,
                 TF,
                 beam,
                 BKL,
                 BKB,
                 A0,
                 kg,
                 Volume,
                 gm,
                 V,
                 rho=1000,
                 g=9.81,
                 phi_max=8,
                 omega0=None,
                 verify_input=True,
                 limit_inputs=False,
                 **kwargs):
        """
        Estimate a roll decay test using the Simplified Ikeda Method to predict roll damping.
        NOTE! This method is currently only valid for zero speed!

        Parameters
        ----------
        lpp
            Ship perpendicular length [m]
        TA
            Draught aft [m]
        TF
            Draught forward [m]
        beam
            Ship b [m]
        BKL
            Bilge keel length [m]
        BKB
            Bilge keel height [m]
        A0
            Middship coefficient (A_m/(B*d) [-]
        kg
            Vertical centre of gravity [m]
        Volume
            Displacement of ship [m3]
        gm
            metacentric height [m]
        V
            ship speed [m/s]
        rho
            Density of water [kg/m3]
        g
            acceleration of gravity [m/s**2]
        phi_max
            max roll angle during test [deg]
        omega0
            Natural frequency of motion [rad/s], if None it will be calculated with fft of signal

        For more info see: "rolldecaysestimators/simplified_ikeda.py"
        """
        super().__init__(omega0=omega0)

        self.lpp = lpp
        self.TA = TA
        self.TF = TF
        self.beam = beam
        self.BKL = BKL
        self.BKB = BKB
        self.A0 = A0
        self.kg = kg
        self.Volume = Volume
        self.V = V
        self.rho = rho
        self.g = g
        self.gm = gm
        self.phi_max = phi_max
        self.two_point_regression = True
        self.verify_input = verify_input
        self.limit_inputs = limit_inputs

    @property
    def zeta_lambda(self):
        return self.functions_ikeda[0]

    @property
    def B44_lambda(self):
        return self.functions_ikeda[1]

    #def simulate(self, t :np.ndarray, phi0 :float, phi1d0 :float,omega0:float, zeta:float)->pd.DataFrame:
    #    """
    #    Simulate a roll decay test using the quadratic method.
    #    :param t: time vector to be simulated [s]
    #    :param phi0: initial roll angle [rad]
    #    :param phi1d0: initial roll speed [rad/s]
    #    :param omega0: roll natural frequency[rad/s]
    #    :param zeta:linear roll damping [-]
    #    :return: pandas data frame with time series of 'phi' and 'phi1d'
    #    """
    #    parameters={
    #        'omega0':omega0,
    #        'zeta':zeta,
    #    }
    #    return self._simulate(t=t, phi0=phi0, phi1d0=phi1d0, parameters=parameters)

    def fit(self, X, y=None, **kwargs):
        self.X = X

        self.phi_max = np.rad2deg(
            self.X[self.phi_key].abs().max())  ## Initial roll angle in [deg]

        DRAFT = (self.TA + self.TF) / 2
        omega0 = self.omega0

        if (self.lpp * self.beam * DRAFT > 0):
            CB = self.Volume / (self.lpp * self.beam * DRAFT)
        else:
            raise IkedaEstimatorFitError('lpp, b or DRAFT is zero or nan!')

        self.ikeda_parameters = {
            'LPP': self.lpp,
            'Beam': self.beam,
            'DRAFT': DRAFT,
            'PHI': self.phi_max,
            'BKL': self.BKL,
            'BKB': self.BKB,
            'OMEGA': omega0,
            'OG': (-self.kg + DRAFT),
            'CB': CB,
            'CMID': self.A0,
            'V': self.V,
            'verify_input': self.verify_input,
            'limit_inputs': self.limit_inputs,
        }

        self.result = self.calculate()

        m = self.Volume * self.rho
        B_44 = self.B44_lambda(B_44_hat=self.result.B_44_HAT,
                               Disp=self.Volume,
                               b=self.beam,
                               g=self.g,
                               rho=self.rho)
        zeta = self.zeta_lambda(B_1=B_44,
                                GM=self.gm,
                                g=self.g,
                                m=m,
                                omega0=omega0)
        self.parameters = {
            'zeta': zeta,
            'omega0': omega0,
            'd': 0,
        }

        self.is_fitted_ = True

    def calculate(self, **kwargs):

        B44HAT, BFHAT, BWHAT, BEHAT, BBKHAT, BLHAT = calculate_roll_damping(
            **self.ikeda_parameters, **kwargs)
        s = pd.Series()
        s['B_44_HAT'] = B44HAT
        s['B_F_HAT'] = BFHAT
        s['B_W_HAT'] = BWHAT
        s['B_E_HAT'] = BEHAT
        s['B_BK_HAT'] = BBKHAT
        s['B_L_HAT'] = BLHAT

        return s

    def result_for_database(self, score=True, **kwargs):

        s = super().result_for_database(score=score, **kwargs)
        s.update(self.result)

        return s
class DirectEstimator(RollDecay):
    """ A template estimator to be used as a reference implementation.

    For more information regarding how to build your own estimator, read more
    in the :ref:`User Guide <user_guide>`.

    Parameters
    ----------
    bounds : dict, default=None
        Boundaries for the parameters expressed as dict:
        Ex: {
            'zeta':(-np.inf, np.inf),
            'd':(0,42),
            }

    fit_method : str, default='integration'
        The fitting method could be either 'integration' or 'derivation'
        'integration' means that the diff equation is solved with ODE integration.
        The curve_fit runs an integration for each set of parameters to get the best fit.

        'derivation' means that the diff equation os solved by first calculating the 1st and 2nd derivatives numerically.
        The derivatives are then inserted into the diff equation and the best fit is found.


    """

    # Defining the diff equation for this estimator:
    rhs = -phi_dot_dot / (omega0 ** 2) - 2 * zeta / omega0 * phi_dot - d * sp.Abs(phi_dot) * phi_dot / (omega0 ** 2)
    roll_diff_equation = sp.Eq(lhs=phi, rhs=rhs)
    acceleration = sp.Eq(lhs=phi, rhs=sp.solve(roll_diff_equation, phi.diff().diff())[0])
    functions = {'acceleration':lambdify(acceleration.rhs)}

    @classmethod
    def load(cls, omega0:float, d:float, zeta:float, X=None):
        """
        Load data and parameters from an existing fitted estimator

        Parameters
        ----------
        omega0 :
            roll frequency [rad/s]
        d : nondimensional linear damping
        zeta :
            nondimensional quadratic damping
        X : pd.DataFrame
            DataFrame containing the measurement that this estimator fits (optional).
        Returns
        -------
        estimator
            Loaded with parameters from data and maybe also a loaded measurement X
        """
        data={
            'd':d,
            'zeta':zeta,
            'omega0':omega0,
        }

        return super(cls, cls)._load(data=data, X=X)

    def calculate_amplitudes_and_damping(self):
        if hasattr(self,'X'):
            if not self.X is None:
                self.X_amplitudes=measure.calculate_amplitudes_and_damping(X=self.X)

                if self.is_fitted_:
                    X_pred = self.predict(X=self.X)
                    self.X_pred_amplitudes = measure.calculate_amplitudes_and_damping(X=X_pred)


    @staticmethod
    def calculate_damping(X_amplitudes):

        df_decrements = pd.DataFrame()

        for i in range(len(X_amplitudes) - 1):
            s1 = X_amplitudes.iloc[i]
            s2 = X_amplitudes.iloc[i + 1]

            decrement = s1 / s2
            decrement.name = s1.name
            df_decrements = df_decrements.append(decrement)

        df_decrements['zeta_n'] = 1 / (2 * np.pi) * np.log(df_decrements['phi'])

        df_decrements['zeta_n'] *= 2  # !!! # Todo: Where did this one come from?



        X_amplitudes_new = X_amplitudes.copy()
        X_amplitudes_new = X_amplitudes_new.iloc[0:-1].copy()
        X_amplitudes_new['zeta_n'] = df_decrements['zeta_n'].copy()
        X_amplitudes_new['B_n'] = 2*X_amplitudes_new['zeta_n']  # [Nm*s]

        return X_amplitudes_new


    def measure_error(self, X):
        y_true, y_pred = self.true_and_prediction(X=X)
        return y_pred - y_true

    def score(self, X=None, y=None, sample_weight=None):
        """
        Return the coefficient of determination R_b^2 of the prediction.

        The coefficient R_b^2 is defined as (1 - u/v), where u is the residual sum of squares
        ((y_true - y_pred) ** 2).sum() and v is the total sum of squares ((y_true - y_true.mean()) ** 2).sum().
        The best possible score is 1.0 and it can be negative (because the model can be arbitrarily worse).
        A constant model that always predicts the expected value of y, disregarding the input features,
        would get a R_b^2 score of 0.0.

        Parameters
        ----------
        X : array-like of shape (n_samples, n_features)
            Test samples. For some estimators this may be a precomputed kernel matrix or a list of generic
            objects instead, shape = (n_samples, n_samples_fitted), where n_samples_fitted is the number of samples
            used in the fitting for the estimator.

        y : Dummy not used

        sample_weight : Dummy

        Returns
        -------
        score : float
            R_b^2 of self.predict(X) wrt. y.

        """

        y_true, y_pred = self.true_and_prediction(X=X)

        #sample_weight = np.abs(y_true)
        #return r2_score(y_true=y_true, y_pred=y_pred,sample_weight=sample_weight)
        return r2_score(y_true=y_true, y_pred=y_pred)

    def calculate_average_linear_damping(self,phi_a=None):
        """
        Calculate the average linear damping
        In line with Himeno this equivalent linear damping is calculated as
        Parameters
        ----------
        phi_a : float, default = None
            linearize around this average angle [rad]
            phi_a is calculated based on data if None

        Returns
        -------
        average_linear_damping

        """
        check_is_fitted(self, 'is_fitted_')

        zeta = self.parameters['zeta']
        d = self.parameters.get('d',0)

        if phi_a is None:
            phi_a = self.X[self.phi_key].abs().mean()

        return zeta + 4/(3*np.pi)*d*phi_a

    def plot_fit(self, ax=None, include_model_test=True, label=None, **kwargs):

        check_is_fitted(self, 'is_fitted_')

        if ax is None:
            fig,ax = plt.subplots()

        df = self.predict(X=self.X)
        df['phi_deg'] = np.rad2deg(df['phi'])

        if include_model_test:
            X = self.X.copy()
            X['phi_deg'] = np.rad2deg(X['phi'])
            X.plot(y=r'phi_deg', ax=ax, label='Model test')

        if not label:
            label = 'fit'
        df.plot(y=r'phi_deg', ax=ax, label=label, style='--',**kwargs)

        ax.legend()
        ax.set_xlabel(r'Time [s]')
        ax.set_ylabel(r'$\Phi$ [deg]')

    def plot_error(self,X=None, ax=None, **kwargs):
        check_is_fitted(self, 'is_fitted_')

        if ax is None:
            fig, ax = plt.subplots()

        error = self.measure_error(X=X)

        ax.plot(self.X.index, error, label=self.__repr__(), **kwargs)
        ax.legend()
        ax.set_xlabel('Time [s]')
        ax.set_ylabel('error: phi_pred - phi_true [rad]')

    def plot_peaks(self, ax=None, **kwargs):
        check_is_fitted(self, 'is_fitted_')

        if ax is None:
            fig,ax = plt.subplots()

        #self.X.plot(y='phi', ax=ax)

        self.X_amplitudes.plot(y='phi', ax=ax, **kwargs)

        #ax.plot([np.min(self.X.index),np.max(self.X.index)],[0,0],'m-')
        ax.set_title('Peaks')

    def plot_velocity(self, ax=None):
        check_is_fitted(self, 'is_fitted_')

        if ax is None:
            fig,ax = plt.subplots()

        self.X.plot(y='phi1d', ax=ax)
        self.X_amplitudes.plot(y='phi1d', ax=ax, style='r.')
        ax.plot([np.min(self.X.index), np.max(self.X.index)], [0, 0], 'm-')
        ax.set_title('Velocities')

    def plot_amplitude(self, ax=None, include_model_test=True):

        if ax is None:
            fig,ax = plt.subplots()

        if not hasattr(self,'X_amplitudes'):
            self.calculate_amplitudes_and_damping()

        if include_model_test:
            X_amplitudes=self.X_amplitudes.copy()
            X_amplitudes['phi_a']=np.rad2deg(X_amplitudes['phi_a'])
            X_amplitudes.plot(y='phi_a', style='o', label='Model test', ax=ax)

        if hasattr(self,'X_pred_amplitudes'):
            label = self.__repr__()
            X_pred_amplitudes = self.X_pred_amplitudes.copy()
            X_pred_amplitudes['phi_a'] = np.rad2deg(X_pred_amplitudes['phi_a'])
            X_pred_amplitudes.plot(y='phi_a', label=label, ax=ax)

    def plot_damping(self, ax=None, include_model_test=True,label=None, **kwargs):

        if ax is None:
            fig, ax = plt.subplots()

        plot = None
        if include_model_test:
            X_amplitudes = measure.calculate_amplitudes_and_damping(X=self.X)
            X_amplitudes['phi_a'] = np.rad2deg(X_amplitudes['phi_a'])
            plot = X_amplitudes.plot(x='phi_a', y='B_n', style='o', label='Model test', ax=ax, **kwargs)

        if self.is_fitted_:
            if not label:
                label = self.__repr__()

            X_pred = self.predict(X=self.X)
            X_pred_amplitudes = measure.calculate_amplitudes_and_damping(X=X_pred)
            X_pred_amplitudes['phi_a'] = np.rad2deg(X_pred_amplitudes['phi_a'])

            if plot is None:
                color=None
            else:
                line = plot.axes.get_lines()[-1]
                color = line.get_color()

            x = X_pred_amplitudes['phi_a']
            y = X_pred_amplitudes['B_n']
            ax.plot(x, y, color=color, label=label, **kwargs, lw=2)

        ax.set_xlabel(r'$\phi_a$ [deg]')
        ax.set_ylabel(r'$B$ [Nms]')
        ax.legend()

    def plot_omega0(self,ax=None, include_model_test=True, label=None, **kwargs):

        if ax is None:
            fig,ax = plt.subplots()

        if not hasattr(self, 'X_amplitudes'):
            self.calculate_amplitudes_and_damping()

        plot = None
        if include_model_test:

            X_amplitudes = self.X_amplitudes.copy()
            X_amplitudes['omega0_norm'] = X_amplitudes['omega0']/self.omega0
            X_amplitudes['phi_a_deg'] = np.rad2deg(X_amplitudes['phi_a'])
            plot = X_amplitudes.plot(x='phi_a_deg', y='omega0_norm', style='o', label='Model test', ax=ax, **kwargs)

        if hasattr(self, 'X_pred_amplitudes'):
            if not label:
                label = self.__repr__()

            if plot is None:
                color = None
            else:
                line = plot.axes.get_lines()[-1]
                color = line.get_color()

            x = np.rad2deg(self.X_pred_amplitudes['phi_a'])
            y = self.X_pred_amplitudes['omega0']/self.omega0

            ax.plot(x, y, color=color, label=label, **kwargs, lw=2)

        ax.set_xlabel(r'$\phi_a$ [deg]')
        ax.set_ylabel(r'$\frac{\omega_0^N}{\omega_0}$ [-]')
        ax.legend()

    def plot_fft(self, ax=None):
        check_is_fitted(self, 'is_fitted_')

        if ax is None:
            fig,ax = plt.subplots()

        frequencies, dft = self.fft(self.X['phi'])
        omega=2*np.pi*frequencies

        omega0 = self.fft_omega0(frequencies=frequencies, dft=dft)
        index = np.argmax(np.abs(dft))
        ax.plot(omega, dft)
        ax.plot(omega0,dft[index],'ro')
 def fit(self, X, y):
     self.X = X
     result = self.polynomial_regression.fit(X=self.good_X(X), y=y)
     self.equation = self.get_equation()
     self.lamda = lambdify(self.equation.rhs)
     return result