def fit(
        self,
        y,
        seasonal_period=None,
        X=None,
        fourier_order=None,
        ma=None,
        alpha=1.0,
        decay=-1,
        n_changepoints=0.25,
        seasonal_lr=0.9,
        rs_lr=1,
        exogenous_lr=1,
        exogenous_estimator=OLS,
        exogenous_params={},
        linear_lr=0.9,
        cov_threshold=0.7,
        moving_medians=False,
        max_rounds=50,
        min_alpha=0.05,
        max_alpha=1.0,
        round_penalty=0.0001,
        trend_penalty=True,
        multiplicative=None,
        changepoints=True,
        smoother=False,
        seasonality_weights=False,
    ):
        """


        Parameters
        ----------
        y : np.array
            the time series as a numpy array.
        seasonal_period : int, optional
            DESCRIPTION. The default is None.
        fourier_order : int, optional
            How many fourier sin/cos pairs to create, the larger the number the more complex of a seasonal pattern can be fitted. A lower number leads to smoother results. This is auto-set based on seasonal_period. The default is None.
        ma : int, optional
            The moving average order to use, this is auto-set based on internal logic. Passing 4 would fit a 4 period moving average on the residual component. The default is None.
        alpha : TYPE, optional
            The alpha which is used in fitting the underlying LASSO when using piecewise functions. The default is 1.0.
        decay : float, optional
            Effects the slopes of the piecewise-linear basis function. The default is -1.
        n_changepoints : float, optional
            The number of changepoint knots to place, a default of .25 with place .25 * series length number of knots. The default is .25.
        seasonal_lr : float, optional
            A shrinkage parameter (0<seasonal_lr<=1) which penalizes the seasonal fit, a .9 will flatly multiply the seasonal fit by .9 each boosting round, this can be used to allow more signal to the exogenous component. The default is .9.
        rs_lr : float, optional
            A shrinkage parameter (0<rs_lr<=1) which penalizes the residual smoothing, a .9 will flatly multiply the residual fit by .9 each boosting round, this can be used to allow more signal to the seasonality or linear components. The default is 1.
        linear_lr : float, optional
            A shrinkage parameter (0<linear_lr<=1) which penalizes the linear trend fit, a .9 will flatly multiply the linear fit by .9 each boosting round, this can be used to allow more signal to the seasonality or exogenous components. The default is .9.
        cov_threshold : float, optional
            The deseasonalized cov is used to auto-set some logic, lowering the cov_threshold will result in simpler and less complex residual smoothing. If you pass something like 1000 then there will be no safeguards applied. The default is .7.
        moving_medians : boolean, optional
            The default behavior is to fit an initial median to the time series, if you pass True to moving_medians then it will fit a median per seasonal period. The default is False.
        max_rounds : int, optional
            The max number of boosting rounds. The boosting will auto-stop but depending on other parameters such as rs_lr you may want more rounds. Generally, the more rounds => the more smooth your fit. The default is 10.
        min_alpha : float, optional
            The min alpha in the SES ensemble. The default is .05.
        max_alpha : float, optional
            The max alpha used in the SES ensemble. The default is 1.0.
        trend_penalty : boolean, optional
            Whether to apply a simple penalty to the lienar trend component, very useful for dealing with the potentially dangerous piecewise trend. The default is True.
        multiplicative : boolean, optional
            Auto-set based on internal logic, but if given True it will simply take the log of the time series. The default is None.
        changepoints : boolean, optional
            Whether to fit for changepoints if all other logic allows for it, by setting False then MFLES will not ever fit a piecewise trend. The default is True.
        smoother : boolean, optional
            If True then a simple exponential ensemble will be used rather than auto settings. The default is False.

        Returns
        -------
        None.

        """
        if cov_threshold == -1:
            cov_threshold = 10000
        n = len(y)
        y = _ensure_float(y)
        self.exogenous_lr = exogenous_lr
        if multiplicative is None:
            if seasonal_period is None:
                multiplicative = False
            else:
                multiplicative = True
            if y.min() <= 0:
                multiplicative = False
        if multiplicative:
            self.const = y.min()
            y = np.log(y)
        else:
            self.const = None
            self.std = np.std(y)
            self.mean = np.mean(y)
            y = y - self.mean
            if self.std > 0:
                y = y / self.std
        if seasonal_period is not None:
            if not isinstance(seasonal_period, list):
                seasonal_period = [seasonal_period]
        if n < 4 or np.all(y == np.mean(y)):
            if self.verbose:
                if n < 4:
                    print("series is too short (<4), defaulting to naive")
                else:
                    print(f"input is constant with value {y[0]}, defaulting to naive")
            self.trend = np.append(y[-1], y[-1])
            self.seasonality = np.zeros(len(y))
            self.trend_penalty = False
            self.mean = y[-1]
            self.std = 0
            self.exo_model = [Zeros()]
            return np.tile(y[-1], len(y))
        og_y = y
        self.og_y = og_y
        y = y.copy()
        if n_changepoints is None:
            changepoints = False
        if isinstance(n_changepoints, float) and n_changepoints < 1:
            n_changepoints = int(n_changepoints * n)
        self.linear_component = np.zeros(n)
        self.seasonal_component = np.zeros(n)
        self.ses_component = np.zeros(n)
        self.median_component = np.zeros(n)
        self.exogenous_component = np.zeros(n)
        self.exo_model = []
        self.round_cost = []
        self.trend_penalty = trend_penalty
        if moving_medians and seasonal_period is not None:
            fitted = median(y, max(seasonal_period))
        else:
            fitted = median(y, None)
        self.median_component += fitted
        self.trend = np.append(fitted.copy()[-1:], fitted.copy()[-1:])
        mse = None
        equal = 0
        if ma is None:
            ma_cycle = itertools.cycle([1])
        else:
            if not isinstance(ma, list):
                ma = [ma]
            ma_cycle = itertools.cycle(ma)
        if seasonal_period is not None:
            seasons_cycle = itertools.cycle(list(range(len(seasonal_period))))
            self.seasonality = np.zeros(max(seasonal_period))
            fourier_series = []
            for period in seasonal_period:
                if fourier_order is None:
                    fourier = set_fourier(period)
                else:
                    fourier = fourier_order
                fourier_series.append(get_fourier_series(n, period, fourier))
            if seasonality_weights:
                cycle_weights = []
                for period in seasonal_period:
                    cycle_weights.append(get_seasonality_weights(y, period))
        else:
            self.seasonality = None
        for i in range(max_rounds):
            resids = y - fitted
            if mse is None:
                mse = calc_mse(y, fitted)
            else:
                if mse <= calc_mse(y, fitted):
                    if equal == 6:
                        break
                    equal += 1
                else:
                    mse = calc_mse(y, fitted)
                self.round_cost.append(mse)
            if seasonal_period is not None:
                seasonal_period_cycle = next(seasons_cycle)
                if seasonality_weights:
                    seas = wls(
                        fourier_series[seasonal_period_cycle],
                        resids,
                        cycle_weights[seasonal_period_cycle],
                    )
                else:
                    seas = ols(fourier_series[seasonal_period_cycle], resids)
                seas = seas * seasonal_lr
                component_mse = calc_mse(y, fitted + seas)
                if mse > component_mse:
                    mse = component_mse
                    fitted += seas
                    resids = y - fitted
                    self.seasonality += np.resize(
                        seas[-seasonal_period[seasonal_period_cycle] :],
                        len(self.seasonality),
                    )
                    self.seasonal_component += seas
            if X is not None and i > 0:
                model_obj = exogenous_estimator(**exogenous_params)
                model_obj.fit(X, resids)
                self.exo_model.append(model_obj)
                _fitted_values = model_obj.predict(X) * exogenous_lr
                self.exogenous_component += _fitted_values
                fitted += _fitted_values
                resids = y - fitted
            if (
                i % 2
            ):  # if even get linear piece, allows for multiple seasonality fitting a bit more
                if self.robust:
                    tren = siegel_repeated_medians(
                        x=np.arange(n, dtype=resids.dtype), y=resids
                    )
                else:
                    if i == 1 or not changepoints:
                        tren = fast_ols(x=np.arange(n), y=resids)
                    else:
                        cps = min(n_changepoints, int(0.1 * n))
                        lbf = get_basis(y=resids, n_changepoints=cps, decay=decay)
                        tren = np.dot(lbf, lasso_nb(lbf, resids, alpha=alpha))
                        tren = tren * linear_lr
                component_mse = calc_mse(y, fitted + tren)
                if mse > component_mse:
                    mse = component_mse
                    fitted += tren
                    self.linear_component += tren
                    self.trend += tren[-2:]
                    if i == 1:
                        self.penalty = calc_rsq(resids, tren)
            elif i > 4 and not i % 2:
                if smoother is None:
                    if seasonal_period is not None:
                        len_check = int(max(seasonal_period))
                    else:
                        len_check = 12
                    if resids[-1] > np.mean(resids[-len_check:-1]) + 3 * np.std(
                        resids[-len_check:-1]
                    ):
                        smoother = 0
                    if resids[-1] < np.mean(resids[-len_check:-1]) - 3 * np.std(
                        resids[-len_check:-1]
                    ):
                        smoother = 0
                    if resids[-2] > np.mean(resids[-len_check:-2]) + 3 * np.std(
                        resids[-len_check:-2]
                    ):
                        smoother = 0
                    if resids[-2] < np.mean(resids[-len_check:-2]) - 3 * np.std(
                        resids[-len_check:-2]
                    ):
                        smoother = 0
                    if smoother is None:
                        smoother = 1
                    else:
                        resids[-2:] = cap_outliers(resids, 3)[-2:]
                tren = ses_ensemble(
                    resids,
                    min_alpha=min_alpha,
                    max_alpha=max_alpha,
                    smooth=smoother * 1,
                    order=next(ma_cycle),
                )
                tren = tren * rs_lr
                component_mse = calc_mse(y, fitted + tren)
                if mse > component_mse + round_penalty * mse:
                    mse = component_mse
                    fitted += tren
                    self.ses_component += tren
                    self.trend += tren[-1]
            if i == 0:  # get deasonalized cov for some heuristic logic
                if self.robust is None:
                    try:
                        if calc_cov(resids, multiplicative) > cov_threshold:
                            self.robust = True
                        else:
                            self.robust = False
                    except:
                        self.robust = True

            if i == 1:
                resids = cap_outliers(
                    resids, 5
                )  # cap extreme outliers after initial rounds
        if multiplicative:
            fitted = np.exp(fitted)
        else:
            fitted = self.mean + (fitted * self.std)
        self.multiplicative = multiplicative
        return fitted



        Parameters
        ----------
        y : np.array
            the time series as a numpy array.
        seasonal_period : int, optional
            DESCRIPTION. The default is None.
        fourier_order : int, optional
            How many fourier sin/cos pairs to create, the larger the number the more complex of a seasonal pattern can be fitted. A lower number leads to smoother results. This is auto-set based on seasonal_period. The default is None.
        ma : int, optional
            The moving average order to use, this is auto-set based on internal logic. Passing 4 would fit a 4 period moving average on the residual component. The default is None.
        alpha : TYPE, optional
            The alpha which is used in fitting the underlying LASSO when using piecewise functions. The default is 1.0.
        decay : float, optional
            Effects the slopes of the piecewise-linear basis function. The default is -1.
        n_changepoints : float, optional
            The number of changepoint knots to place, a default of .25 with place .25 * series length number of knots. The default is .25.
        seasonal_lr : float, optional
            A shrinkage parameter (0<seasonal_lr<=1) which penalizes the seasonal fit, a .9 will flatly multiply the seasonal fit by .9 each boosting round, this can be used to allow more signal to the exogenous component. The default is .9.
        rs_lr : float, optional
            A shrinkage parameter (0<rs_lr<=1) which penalizes the residual smoothing, a .9 will flatly multiply the residual fit by .9 each boosting round, this can be used to allow more signal to the seasonality or linear components. The default is 1.
        linear_lr : float, optional
            A shrinkage parameter (0<linear_lr<=1) which penalizes the linear trend fit, a .9 will flatly multiply the linear fit by .9 each boosting round, this can be used to allow more signal to the seasonality or exogenous components. The default is .9.
        cov_threshold : float, optional
            The deseasonalized cov is used to auto-set some logic, lowering the cov_threshold will result in simpler and less complex residual smoothing. If you pass something like 1000 then there will be no safeguards applied. The default is .7.
        moving_medians : boolean, optional
            The default behavior is to fit an initial median to the time series, if you pass True to moving_medians then it will fit a median per seasonal period. The default is False.
        max_rounds : int, optional
            The max number of boosting rounds. The boosting will auto-stop but depending on other parameters such as rs_lr you may want more rounds. Generally, the more rounds => the more smooth your fit. The default is 10.
        min_alpha : float, optional
            The min alpha in the SES ensemble. The default is .05.
        max_alpha : float, optional
            The max alpha used in the SES ensemble. The default is 1.0.
        trend_penalty : boolean, optional
            Whether to apply a simple penalty to the lienar trend component, very useful for dealing with the potentially dangerous piecewise trend. The default is True.
        multiplicative : boolean, optional
            Auto-set based on internal logic, but if given True it will simply take the log of the time series. The default is None.
        changepoints : boolean, optional
            Whether to fit for changepoints if all other logic allows for it, by setting False then MFLES will not ever fit a piecewise trend. The default is True.
        smoother : boolean, optional
            If True then a simple exponential ensemble will be used rather than auto settings. The default is False.

        Returns
        -------
        None.

        
python/statsforecast/mfles.py