docstr: A function for forecasting.
  It captures growth, seasonality, holidays and other patterns.
  See "Capturing the time-dependence in the precipitation process for
  weather risk assessment" as a reference:
  https://link.springer.com/article/10.1007/s00477-016-1285-8
  
  Parameters
  ----------
  df : `pandas.DataFrame`
      A data frame which includes the timestamp column
      as well as the value column.
  time_col : `str`
      The column name in ``df`` representing time for the time series data.
      The time column can be anything that can be parsed by pandas DatetimeIndex.
  value_col: `str`
      The column name which has the value of interest to be forecasted.
  freq: `str`, optional, default None
      The intended timeseries frequency, DateOffset alias.
      See https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases.
      If None automatically inferred. This frequency will be passed through
      this function as a part of the trained model and used at predict time
      if needed.
      If data include missing timestamps, and frequency is monthly/annual,
      user should pass this parameter, as it cannot be inferred.
  origin_for_time_vars : `float`, optional, default None
      The time origin used to create continuous variables for time.
      If None, uses the first record in ``df``.
  extra_pred_cols : `list` of `str`, default None
      Names of the extra predictor columns.
  
      If None, uses ["ct1"], a simple linear growth term.
  
      It can leverage regressors included in ``df`` and those generated
      by the other parameters. The following effects will not be modeled
      unless specified in ``extra_pred_cols``:
  
          - included in ``df``: e.g. macro-economic factors, related timeseries
          - from `~greykite.common.features.timeseries_features.build_time_features_df`:
            e.g. ct1, ct_sqrt, dow, ...
          - from ``daily_event_df_dict``: e.g. "events_India", ...
  
      The columns corresponding to the following parameters are included
      in the model without specification in ``extra_pred_cols``.
      ``extra_pred_cols`` can be used to add interactions with these terms.
  
          changepoints_dict: e.g. changepoint0, changepoint1, ...
          fs_components_df: e.g. sin2_dow, cos4_dow_weekly
          autoreg_dict: e.g. x_lag1, x_avglag_2_3_4, y_avglag_1_to_5
  
      If a regressor is passed in ``df``, it needs to be provided to
      the associated predict function:
  
          ``predict_silverkite``: via ``fut_df`` or ``new_external_regressor_df``
          ``silverkite.predict_n(_no_sim``: via ``new_external_regressor_df``
  drop_pred_cols : `list` [`str`] or None, default None
      Names of predictor columns to be dropped from the final model.
      Ignored if None
  explicit_pred_cols : `list` [`str`] or None, default None
      Names of the explicit predictor columns which will be
      the only variables in the final model. Note that this overwrites
      the generated predictors in the model and may include new
      terms not appearing in the predictors (e.g. interaction terms).
      Ignored if None
  train_test_thresh : `datetime.datetime`, optional
      e.g. datetime.datetime(2019, 6, 30)
      The threshold for training and testing split.
      Note that the final returned model is trained using all data.
      If None, training split is based on ``training_fraction``
  training_fraction : `float`, optional
      The fraction of data used for training (0.0 to 1.0)
      Used only if ``train_test_thresh`` is None.
      If this is also None or 1.0, then we skip testing
      and train on the entire dataset.
  fit_algorithm : `str`, optional, default "linear"
      The type of predictive model used in fitting.
  
      See `~greykite.algo.common.ml_models.fit_model_via_design_matrix`
      for available options and their parameters.
  fit_algorithm_params : `dict` or None, optional, default None
      Parameters passed to the requested fit_algorithm.
      If None, uses the defaults in `~greykite.algo.common.ml_models.fit_model_via_design_matrix`.
  daily_event_df_dict : `dict` or None, optional, default None
      A dictionary of data frames, each representing events data for the corresponding key.
      The DataFrame has two columns:
  
          - The first column contains event dates. Must be in a format
            recognized by `pandas.to_datetime`. Must be at daily
            frequency for proper join. It is joined against the time
            in ``df``, converted to a day:
            ``pd.to_datetime(pd.DatetimeIndex(df[time_col]).date)``.
          - the second column contains the event label for each date
  
      The column order is important; column names are ignored.
      The event dates must span their occurrences in both the training
      and future prediction period.
  
      During modeling, each key in the dictionary is mapped to a categorical variable
      named ``f"{EVENT_PREFIX}_{key}"``, whose value at each timestamp is specified
      by the corresponding DataFrame.
  
      For example, to manually specify a yearly event on September 1
      during a training/forecast period that spans 2020-2022::
  
          daily_event_df_dict = {
              "custom_event": pd.DataFrame({
                  "date": ["2020-09-01", "2021-09-01", "2022-09-01"],
                  "label": ["is_event", "is_event", "is_event"]
              })
          }
  
      It's possible to specify multiple events in the same df. Two events,
      ``"sep"`` and ``"oct"`` are specified below for 2020-2021::
  
          daily_event_df_dict = {
              "custom_event": pd.DataFrame({
                  "date": ["2020-09-01", "2020-10-01", "2021-09-01", "2021-10-01"],
                  "event_name": ["sep", "oct", "sep", "oct"]
              })
          }
  
      Use multiple keys if two events may fall on the same date. These events
      must be in separate DataFrames::
  
          daily_event_df_dict = {
              "fixed_event": pd.DataFrame({
                  "date": ["2020-09-01", "2021-09-01", "2022-09-01"],
                  "event_name": "fixed_event"
              }),
              "moving_event": pd.DataFrame({
                  "date": ["2020-09-01", "2021-08-28", "2022-09-03"],
                  "event_name": "moving_event"
              }),
          }
  
      The multiple event specification can be used even if events never overlap. An
      equivalent specification to the second example::
  
          daily_event_df_dict = {
              "sep": pd.DataFrame({
                  "date": ["2020-09-01", "2021-09-01"],
                  "event_name": "is_event"
              }),
              "oct": pd.DataFrame({
                  "date": ["2020-10-01", "2021-10-01"],
                  "event_name": "is_event"
              }),
          }
  
      .. note::
  
          The events you want to use must be specified in ``extra_pred_cols``.
          These take the form: ``f"{EVENT_PREFIX}_{key}"``, where
          `~greykite.common.constants.EVENT_PREFIX` is the constant.
  
          Do not use `~greykite.common.constants.EVENT_DEFAULT`
          in the second column. This is reserved to indicate dates that do not
          correspond to an event.
  daily_event_neighbor_impact : `int`, `list` [`int`], callable or None, default None
      The impact of neighboring timestamps of the events in ``event_df_dict``.
      This is for daily events so the units below are all in days.
  
      For example, if the data is weekly ("W-SUN") and an event is daily,
      it may not exactly fall on the weekly date.
      But you can specify for New Year's day on 1/1, it affects all dates
      in the week, e.g. 12/31, 1/1, ..., 1/6, then it will be mapped to the weekly date.
      In this case you may want to map a daily event's date to a few dates,
      and can specify
      ``neighbor_impact=lambda x: [x-timedelta(days=x.isocalendar()[2]-1) + timedelta(days=i) for i in range(7)]``.
  
      Another example is that the data is rolling 7 day daily data,
      thus a holiday may affect the t, t+1, ..., t+6 dates.
      You can specify ``neighbor_impact=7``.
  
      If input is `int`, the mapping is t, t+1, ..., t+neighbor_impact-1.
      If input is `list`, the mapping is [t+x for x in neighbor_impact].
      If input is a function, it maps each daily event's date to a list of dates.
  daily_event_shifted_effect : `list` [`str`] or None, default None
      Additional neighbor events based on given events.
      For example, passing ["-1D", "7D"] will add extra daily events which are 1 day before
      and 7 days after the given events.
      Offset format is {d}{freq} with any integer plus a frequency string.
      Must be parsable by pandas ``to_offset``.
      The new events' names will be the current events' names with suffix "{offset}_before" or "{offset}_after".
      For example, if we have an event named "US_Christmas Day",
      a "7D" shift will have name "US_Christmas Day_7D_after".
      This is useful when you expect an offset of the current holidays also has impact on the
      time series, or you want to interact the lagged terms with autoregression.
      If ``daily_event_neighbor_impact`` is also specified, this will be applied after adding neighboring days.
  fs_components_df : `pandas.DataFrame` or None, optional
      A dataframe with information about fourier series generation.
      Must contain columns with following names:
  
          "name": name of the timeseries feature e.g. "tod", "tow" etc.
          "period": Period of the fourier series, optional, default 1.0
          "order": Order of the fourier series, optional, default 1.0
          "seas_names": season names corresponding to the name
          (e.g. "daily", "weekly" etc.), optional.
  
      Default includes daily, weekly , yearly seasonality.
  autoreg_dict : `dict` or `str` or None, optional, default `None`
      If a `dict`: A dictionary with arguments for `~greykite.common.features.timeseries_lags.build_autoreg_df`.
      That function's parameter ``value_col`` is inferred from the input of
      current function ``self.forecast``. Other keys are:
  
          ``"lag_dict"`` : `dict` or None
          ``"agg_lag_dict"`` : `dict` or None
          ``"series_na_fill_func"`` : callable
  
      If a `str`: The string will represent a method and a dictionary will be
      constructed using that `str`.
      Currently only implemented method is "auto" which uses
      `~greykite.algo.forecast.silverkite.SilverkiteForecast.__get_default_autoreg_dict`
      to create a dictionary.
      See more details for above parameters in
      `~greykite.common.features.timeseries_lags.build_autoreg_df`.
  past_df : `pandas.DataFrame` or None, default None
      The past df used for building autoregression features.
      This is not necessarily needed since imputation is possible.
      However, it is recommended to provide ``past_df`` for more accurate
      autoregression features and faster training (by skipping imputation).
      The columns are:
  
          time_col : `pandas.Timestamp` or `str`
              The timestamps.
          value_col : `float`
              The past values.
          addition_regressor_cols : `float`
              Any additional regressors.
  
      Note that this ``past_df`` is assumed to immediately precede ``df`` without gaps,
      otherwise an error will be raised.
  lagged_regressor_dict : `dict` or None, default None
      A dictionary with arguments for `~greykite.common.features.timeseries_lags.build_autoreg_df_multi`.
      The keys of the dictionary are the target lagged regressor column names.
      It can leverage the regressors included in ``df``.
      The value of each key is either a `dict` or `str`.
      If `dict`, it has the following keys:
  
          ``"lag_dict"`` : `dict` or None
          ``"agg_lag_dict"`` : `dict` or None
          ``"series_na_fill_func"`` : callable
  
      If `str`, it represents a method and a dictionary will be constructed using that `str`.
      Currently the only implemented method is "auto" which uses
      `~greykite.algo.forecast.silverkite.SilverkiteForecast.__get_default_lagged_regressor_dict`
      to create a dictionary for each lagged regressor.
      An example::
  
          lagged_regressor_dict = {
              "regressor1": {
                  "lag_dict": {"orders": [1, 2, 3]},
                  "agg_lag_dict": {
                      "orders_list": [[7, 7 * 2, 7 * 3]],
                      "interval_list": [(8, 7 * 2)]},
                  "series_na_fill_func": lambda s: s.bfill().ffill()},
              "regressor2": "auto"}
  
      Check the docstring of `~greykite.common.features.timeseries_lags.build_autoreg_df_multi`
      for more details for each argument.
  changepoints_dict : `dict` or None, optional, default None
      Specifies the changepoint configuration.
  
      "method": `str`
          The method to locate changepoints.
          Valid options:
  
              - "uniform". Places n_changepoints evenly spaced changepoints to allow growth to change.
              - "custom". Places changepoints at the specified dates.
              - "auto". Automatically detects change points. For configuration, see
                `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_trend_changepoints`
  
          Additional keys to provide parameters for each particular method are described below.
      "continuous_time_col": `str`, optional
          Column to apply ``growth_func`` to, to generate changepoint features
          Typically, this should match the growth term in the model
      "growth_func": Optional[func]
          Growth function (scalar -> scalar). Changepoint features are created
          by applying ``growth_func`` to ``continuous_time_col`` with offsets.
          If None, uses identity function to use ``continuous_time_col`` directly
          as growth term
          If changepoints_dict["method"] == "uniform", this other key is required:
  
              ``"n_changepoints"``: int
                  number of changepoints to evenly space across training period
  
          If changepoints_dict["method"] == "custom", this other key is required:
  
              ``"dates"``: Iterable[Union[int, float, str, datetime]]
                  Changepoint dates. Must be parsable by pd.to_datetime.
                  Changepoints are set at the closest time on or after these dates
                  in the dataset.
  
          If changepoints_dict["method"] == "auto", the keys that matches the parameters in
          `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_trend_changepoints`,
          except ``df``, ``time_col`` and ``value_col``, are optional.
          Extra keys also include "dates", "combine_changepoint_min_distance" and "keep_detected" to specify
          additional custom trend changepoints. These three parameters correspond to the three parameters
          "custom_changepoint_dates", "min_distance" and "keep_detected" in
          `~greykite.algo.changepoint.adalasso.changepoints_utils.combine_detected_and_custom_trend_changepoints`.
  
  seasonality_changepoints_dict : `dict` or None, default `None`
      The parameter dictionary for seasonality change point detection. Parameters are in
      `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_seasonality_changepoints`.
      Note ``df``, ``time_col``, ``value_col`` and ``trend_changepoints`` are auto populated,
      and do not need to be provided.
  changepoint_detector : `ChangepointDetector` or `None`, default `None`
      The ChangepointDetector class
      :class:`~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector`.
      This is specifically for
      `~greykite.algo.forecast.silverkite.forecast_simple_silverkite.forecast_simple_silverkite`
      to pass the ChangepointDetector class for plotting purposes, in case that users use
      ``forecast_simple_silverkite`` with ``changepoints_dict["method"] == "auto"``. The
      trend change point detection has to be run there to include possible interaction terms,
      so we need to pass the detection result from there to include in the output.
  min_admissible_value : `float` or None, optional, default `None`
      The minimum admissible value to return during prediction.
      If None, no limit is applied.
  max_admissible_value : `float` or None, optional, default `None`
      The maximum admissible value to return during prediction.
      If None, no limit is applied.
  uncertainty_dict : `dict` or None, optional, default `None`
      How to fit the uncertainty model. A dictionary with keys:
          ``"uncertainty_method"`` : `str`
              The title of the method.
              Only "simple_conditional_residuals" is implemented
              in ``fit_ml_model`` which calculates CIs using residuals
          ``"params"`` : `dict`
              A dictionary of parameters needed for
              the requested ``uncertainty_method``. For example, for
              ``uncertainty_method="simple_conditional_residuals"``, see
              parameters of `~greykite.algo.uncertainty.conditional.conf_interval.conf_interval`:
  
                  ``"conditional_cols"``
                  ``"quantiles"``
                  ``"quantile_estimation_method"``
                  ``"sample_size_thresh"``
                  ``"small_sample_size_method"``
                  ``"small_sample_size_quantile"``
  
      If None, no uncertainty intervals are calculated.
  normalize_method : `str` or None, default None
      If a string is provided, it will be used as the normalization method
      in `~greykite.common.features.normalize.normalize_df`, passed via
      the argument ``method``.
      Available options are: "zero_to_one", "statistical", "minus_half_to_half", "zero_at_origin".
      If None, no normalization will be performed.
      See that function for more details.
  adjust_anomalous_dict : `dict` or None, default None
      If not None, a dictionary with following items:
  
          - "func" : `callable`
              A function to perform adjustment of anomalous data with following
              signature::
  
                  adjust_anomalous_dict["func"](
                      df=df,
                      time_col=time_col,
                      value_col=value_col,
                      **params) ->
                  {"adjusted_df": adjusted_df, ...}
  
          - "params" : `dict`
              The extra parameters to be passed to the function above.
  impute_dict : `dict` or None, default None
      If not None, a dictionary with following items:
  
      - "func" : `callable`
          A function to perform imputations with following
          signature::
  
              impute_dict["func"](
                  df=df,
                  value_col=value_col,
                  **impute_dict["params"] ->
              {"df": imputed_df, ...}
  
      - "params" : `dict`
          The extra parameters to be passed to the function above.
  
  regression_weight_col : `str` or None, default None
      The column name for the weights to be used in weighted regression version
      of applicable machine-learning models.
  forecast_horizon : `int` or None, default None
      The number of periods for which forecast is needed.
      Note that this is only used in deciding what parameters should be
      used for certain components e.g. autoregression, if automatic methods
      are requested. While, the prediction time forecast horizon could be different
      from this variable, ideally they should be the same.
  simulation_based : `bool`, default False
      Boolean to specify if the future predictions are to be using simulations
      or not.
      Note that this is only used in deciding what parameters should be
      used for certain components e.g. autoregression, if automatic methods
      are requested. However, the auto-settings and the prediction settings
      regarding using simulations should match.
  simulation_num : `int`, default 10
      The number of simulations for when simulations are used for generating
      forecasts and prediction intervals.
  fast_simulation: `bool`, default False
      Deterimes if fast simulations are to be used. This only impacts models
      which include auto-regression. This method will only generate one simulation
      without any error being added and then add the error using the volatility
      model. The advantage is a major boost in speed during inference and the
      disadvantage is potentially less accurate prediction intervals.
  remove_intercept : `bool`, default False
      Whether to remove explicit and implicit intercepts.
      By default, `patsy` will make the design matrix always full rank.
      It will always include an intercept term unless we specify "-1" or "+0".
      However, if there are categorical variables, even we specify "-1" or "+0",
      it will include an implicit intercept by adding all levels of a categorical
      variable into the design matrix.
      Sometimes we don't want this to happen.
      Setting this parameter to True will remove both explicit and implicit intercepts.
  
  Returns
  -------
  trained_model : `dict`
      A dictionary that includes the fitted model from the function
      :func:`~greykite.algo.common.ml_models.fit_ml_model_with_evaluation`.
      The keys are:
  
          df_dropna: `pandas.DataFrame`
              The ``df`` with NAs dropped.
          df: `pandas.DataFrame`
              The original ``df``.
          num_training_points: `int`
              The number of training points.
          features_df: `pandas.DataFrame`
              The ``df`` with augmented time features.
          min_timestamp: `pandas.Timestamp`
              The minimum timestamp in data.
          max_timestamp: `pandas.Timestamp`
              The maximum timestamp in data.
          freq: `str`
              The data frequency.
          inferred_freq: `str`
              The data freqency inferred from data.
          inferred_freq_in_secs : `float`
              The data frequency inferred from data in seconds.
          inferred_freq_in_days: `float`
              The data frequency inferred from data in days.
          time_col: `str`
              The time column name.
          value_col: `str`
              The value column name.
          origin_for_time_vars: `float`
              The first time stamp converted to a float number.
          fs_components_df: `pandas.DataFrame`
              The dataframe that specifies the seasonality Fourier configuration.
          autoreg_dict: `dict`
              The dictionary that specifies the autoregression configuration.
          lagged_regressor_dict: `dict`
              The dictionary that specifies the lagged regressors configuration.
          lagged_regressor_cols: `list` [`str`]
              List of regressor column names used for lagged regressor
          normalize_method: `str`
              The normalization method.
              See the function input parameter ``normalize_method``.
          daily_event_df_dict: `dict`
              The dictionary that specifies daily events configuration.
          changepoints_dict: `dict`
              The dictionary that specifies changepoints configuration.
          changepoint_values: `list` [`float`]
              The list of changepoints in continuous time values.
          normalized_changepoint_values : `list` [`float`]
              The list of changepoints in continuous time values normalized to 0 to 1.
          continuous_time_col: `str`
              The continuous time column name in ``features_df``.
          growth_func: `func`
              The growth function used in changepoints, None is linear function.
          fs_func: `func`
              The function used to generate Fourier series for seasonality.
          has_autoreg_structure: `bool`
              Whether the model has autoregression structure.
          autoreg_func: `func`
              The function to generate autoregression columns.
          min_lag_order: `int`
              Minimal lag order in autoregression.
          max_lag_order: `int`
              Maximal lag order in autoregression.
          has_lagged_regressor_structure: `bool`
              Whether the model has lagged regressor structure.
          lagged_regressor_func: `func`
              The function to generate lagged regressor columns.
          min_lagged_regressor_order: `int`
              Minimal lag order in lagged regressors.
          max_lagged_regressor_order: `int`
              Maximal lag order in lagged regressors.
          uncertainty_dict: `dict`
              The dictionary that specifies uncertainty model configuration.
          pred_cols: `list` [`str`]
              List of predictor names.
          last_date_for_fit: `str` or `pandas.Timestamp`
              The last timestamp used for fitting.
          trend_changepoint_dates: `list` [`pandas.Timestamp`]
              List of trend changepoints.
          changepoint_detector: `class`
              The `ChangepointDetector` class used to detected trend changepoints.
          seasonality_changepoint_dates: `list` [`pandas.Timestamp`]
              List of seasonality changepoints.
          seasonality_changepoint_result: `dict`
              The seasonality changepoint detection results.
          fit_algorithm: `str`
              The algorithm used to fit the model.
          fit_algorithm_params: `dict`
              The dictionary of parameters for ``fit_algorithm``.
          adjust_anomalous_info: `dict`
              A dictionary that has anomaly adjustment results.
          impute_info: `dict`
              A dictionary that has the imputation results.
          forecast_horizon: `int`
              The forecast horizon in steps.
          forecast_horizon_in_days: `float`
              The forecast horizon in days.
          forecast_horizon_in_timedelta: `datetime.timmdelta`
              The forecast horizon in timedelta.
          simulation_based: `bool`
              Whether to use simulation in prediction with autoregression terms.
          simulation_num : `int`, default 10
              The number of simulations for when simulations are used for generating
              forecasts and prediction intervals.
          train_df : `pandas.DataFrame`
              The past dataframe used to generate AR terms.
              It includes the concatenation of ``past_df`` and ``df`` if ``past_df`` is provided,
              otherwise it is the ``df`` itself.
          drop_intercept_col : `str` or None
              The intercept column, explicit or implicit, to be dropped.
code: def forecast(
      self,
      df,
      time_col,
      value_col,
      freq=None,
      origin_for_time_vars=None,
      extra_pred_cols=None,
      drop_pred_cols=None,
      explicit_pred_cols=None,
      train_test_thresh=None,
      training_fraction=0.9,  # This is for internal ML models validation. The final returned model will be trained on all data.
      fit_algorithm="linear",
      fit_algorithm_params=None,
      daily_event_df_dict=None,
      daily_event_neighbor_impact=None,
      daily_event_shifted_effect=None,
      fs_components_df=pd.DataFrame(
          {
              "name": [
                  TimeFeaturesEnum.tod.value,
                  TimeFeaturesEnum.tow.value,
                  TimeFeaturesEnum.toy.value,
              ],
              "period": [24.0, 7.0, 1.0],
              "order": [3, 3, 5],
              "seas_names": ["daily", "weekly", "yearly"],
          }
      ),
      autoreg_dict=None,
      past_df=None,
      lagged_regressor_dict=None,
      changepoints_dict=None,
      seasonality_changepoints_dict=None,
      changepoint_detector=None,
      min_admissible_value=None,
      max_admissible_value=None,
      uncertainty_dict=None,
      normalize_method=None,
      adjust_anomalous_dict=None,
      impute_dict=None,
      regression_weight_col=None,
      forecast_horizon=None,
      simulation_based=False,
      simulation_num=10,
      fast_simulation=False,
      remove_intercept=False,
  ):
      """A function for forecasting.
      It captures growth, seasonality, holidays and other patterns.
      See "Capturing the time-dependence in the precipitation process for
      weather risk assessment" as a reference:
      https://link.springer.com/article/10.1007/s00477-016-1285-8
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          A data frame which includes the timestamp column
          as well as the value column.
      time_col : `str`
          The column name in ``df`` representing time for the time series data.
          The time column can be anything that can be parsed by pandas DatetimeIndex.
      value_col: `str`
          The column name which has the value of interest to be forecasted.
      freq: `str`, optional, default None
          The intended timeseries frequency, DateOffset alias.
          See https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases.
          If None automatically inferred. This frequency will be passed through
          this function as a part of the trained model and used at predict time
          if needed.
          If data include missing timestamps, and frequency is monthly/annual,
          user should pass this parameter, as it cannot be inferred.
      origin_for_time_vars : `float`, optional, default None
          The time origin used to create continuous variables for time.
          If None, uses the first record in ``df``.
      extra_pred_cols : `list` of `str`, default None
          Names of the extra predictor columns.
  
          If None, uses ["ct1"], a simple linear growth term.
  
          It can leverage regressors included in ``df`` and those generated
          by the other parameters. The following effects will not be modeled
          unless specified in ``extra_pred_cols``:
  
              - included in ``df``: e.g. macro-economic factors, related timeseries
              - from `~greykite.common.features.timeseries_features.build_time_features_df`:
                e.g. ct1, ct_sqrt, dow, ...
              - from ``daily_event_df_dict``: e.g. "events_India", ...
  
          The columns corresponding to the following parameters are included
          in the model without specification in ``extra_pred_cols``.
          ``extra_pred_cols`` can be used to add interactions with these terms.
  
              changepoints_dict: e.g. changepoint0, changepoint1, ...
              fs_components_df: e.g. sin2_dow, cos4_dow_weekly
              autoreg_dict: e.g. x_lag1, x_avglag_2_3_4, y_avglag_1_to_5
  
          If a regressor is passed in ``df``, it needs to be provided to
          the associated predict function:
  
              ``predict_silverkite``: via ``fut_df`` or ``new_external_regressor_df``
              ``silverkite.predict_n(_no_sim``: via ``new_external_regressor_df``
      drop_pred_cols : `list` [`str`] or None, default None
          Names of predictor columns to be dropped from the final model.
          Ignored if None
      explicit_pred_cols : `list` [`str`] or None, default None
          Names of the explicit predictor columns which will be
          the only variables in the final model. Note that this overwrites
          the generated predictors in the model and may include new
          terms not appearing in the predictors (e.g. interaction terms).
          Ignored if None
      train_test_thresh : `datetime.datetime`, optional
          e.g. datetime.datetime(2019, 6, 30)
          The threshold for training and testing split.
          Note that the final returned model is trained using all data.
          If None, training split is based on ``training_fraction``
      training_fraction : `float`, optional
          The fraction of data used for training (0.0 to 1.0)
          Used only if ``train_test_thresh`` is None.
          If this is also None or 1.0, then we skip testing
          and train on the entire dataset.
      fit_algorithm : `str`, optional, default "linear"
          The type of predictive model used in fitting.
  
          See `~greykite.algo.common.ml_models.fit_model_via_design_matrix`
          for available options and their parameters.
      fit_algorithm_params : `dict` or None, optional, default None
          Parameters passed to the requested fit_algorithm.
          If None, uses the defaults in `~greykite.algo.common.ml_models.fit_model_via_design_matrix`.
      daily_event_df_dict : `dict` or None, optional, default None
          A dictionary of data frames, each representing events data for the corresponding key.
          The DataFrame has two columns:
  
              - The first column contains event dates. Must be in a format
                recognized by `pandas.to_datetime`. Must be at daily
                frequency for proper join. It is joined against the time
                in ``df``, converted to a day:
                ``pd.to_datetime(pd.DatetimeIndex(df[time_col]).date)``.
              - the second column contains the event label for each date
  
          The column order is important; column names are ignored.
          The event dates must span their occurrences in both the training
          and future prediction period.
  
          During modeling, each key in the dictionary is mapped to a categorical variable
          named ``f"{EVENT_PREFIX}_{key}"``, whose value at each timestamp is specified
          by the corresponding DataFrame.
  
          For example, to manually specify a yearly event on September 1
          during a training/forecast period that spans 2020-2022::
  
              daily_event_df_dict = {
                  "custom_event": pd.DataFrame({
                      "date": ["2020-09-01", "2021-09-01", "2022-09-01"],
                      "label": ["is_event", "is_event", "is_event"]
                  })
              }
  
          It's possible to specify multiple events in the same df. Two events,
          ``"sep"`` and ``"oct"`` are specified below for 2020-2021::
  
              daily_event_df_dict = {
                  "custom_event": pd.DataFrame({
                      "date": ["2020-09-01", "2020-10-01", "2021-09-01", "2021-10-01"],
                      "event_name": ["sep", "oct", "sep", "oct"]
                  })
              }
  
          Use multiple keys if two events may fall on the same date. These events
          must be in separate DataFrames::
  
              daily_event_df_dict = {
                  "fixed_event": pd.DataFrame({
                      "date": ["2020-09-01", "2021-09-01", "2022-09-01"],
                      "event_name": "fixed_event"
                  }),
                  "moving_event": pd.DataFrame({
                      "date": ["2020-09-01", "2021-08-28", "2022-09-03"],
                      "event_name": "moving_event"
                  }),
              }
  
          The multiple event specification can be used even if events never overlap. An
          equivalent specification to the second example::
  
              daily_event_df_dict = {
                  "sep": pd.DataFrame({
                      "date": ["2020-09-01", "2021-09-01"],
                      "event_name": "is_event"
                  }),
                  "oct": pd.DataFrame({
                      "date": ["2020-10-01", "2021-10-01"],
                      "event_name": "is_event"
                  }),
              }
  
          .. note::
  
              The events you want to use must be specified in ``extra_pred_cols``.
              These take the form: ``f"{EVENT_PREFIX}_{key}"``, where
              `~greykite.common.constants.EVENT_PREFIX` is the constant.
  
              Do not use `~greykite.common.constants.EVENT_DEFAULT`
              in the second column. This is reserved to indicate dates that do not
              correspond to an event.
      daily_event_neighbor_impact : `int`, `list` [`int`], callable or None, default None
          The impact of neighboring timestamps of the events in ``event_df_dict``.
          This is for daily events so the units below are all in days.
  
          For example, if the data is weekly ("W-SUN") and an event is daily,
          it may not exactly fall on the weekly date.
          But you can specify for New Year's day on 1/1, it affects all dates
          in the week, e.g. 12/31, 1/1, ..., 1/6, then it will be mapped to the weekly date.
          In this case you may want to map a daily event's date to a few dates,
          and can specify
          ``neighbor_impact=lambda x: [x-timedelta(days=x.isocalendar()[2]-1) + timedelta(days=i) for i in range(7)]``.
  
          Another example is that the data is rolling 7 day daily data,
          thus a holiday may affect the t, t+1, ..., t+6 dates.
          You can specify ``neighbor_impact=7``.
  
          If input is `int`, the mapping is t, t+1, ..., t+neighbor_impact-1.
          If input is `list`, the mapping is [t+x for x in neighbor_impact].
          If input is a function, it maps each daily event's date to a list of dates.
      daily_event_shifted_effect : `list` [`str`] or None, default None
          Additional neighbor events based on given events.
          For example, passing ["-1D", "7D"] will add extra daily events which are 1 day before
          and 7 days after the given events.
          Offset format is {d}{freq} with any integer plus a frequency string.
          Must be parsable by pandas ``to_offset``.
          The new events' names will be the current events' names with suffix "{offset}_before" or "{offset}_after".
          For example, if we have an event named "US_Christmas Day",
          a "7D" shift will have name "US_Christmas Day_7D_after".
          This is useful when you expect an offset of the current holidays also has impact on the
          time series, or you want to interact the lagged terms with autoregression.
          If ``daily_event_neighbor_impact`` is also specified, this will be applied after adding neighboring days.
      fs_components_df : `pandas.DataFrame` or None, optional
          A dataframe with information about fourier series generation.
          Must contain columns with following names:
  
              "name": name of the timeseries feature e.g. "tod", "tow" etc.
              "period": Period of the fourier series, optional, default 1.0
              "order": Order of the fourier series, optional, default 1.0
              "seas_names": season names corresponding to the name
              (e.g. "daily", "weekly" etc.), optional.
  
          Default includes daily, weekly , yearly seasonality.
      autoreg_dict : `dict` or `str` or None, optional, default `None`
          If a `dict`: A dictionary with arguments for `~greykite.common.features.timeseries_lags.build_autoreg_df`.
          That function's parameter ``value_col`` is inferred from the input of
          current function ``self.forecast``. Other keys are:
  
              ``"lag_dict"`` : `dict` or None
              ``"agg_lag_dict"`` : `dict` or None
              ``"series_na_fill_func"`` : callable
  
          If a `str`: The string will represent a method and a dictionary will be
          constructed using that `str`.
          Currently only implemented method is "auto" which uses
          `~greykite.algo.forecast.silverkite.SilverkiteForecast.__get_default_autoreg_dict`
          to create a dictionary.
          See more details for above parameters in
          `~greykite.common.features.timeseries_lags.build_autoreg_df`.
      past_df : `pandas.DataFrame` or None, default None
          The past df used for building autoregression features.
          This is not necessarily needed since imputation is possible.
          However, it is recommended to provide ``past_df`` for more accurate
          autoregression features and faster training (by skipping imputation).
          The columns are:
  
              time_col : `pandas.Timestamp` or `str`
                  The timestamps.
              value_col : `float`
                  The past values.
              addition_regressor_cols : `float`
                  Any additional regressors.
  
          Note that this ``past_df`` is assumed to immediately precede ``df`` without gaps,
          otherwise an error will be raised.
      lagged_regressor_dict : `dict` or None, default None
          A dictionary with arguments for `~greykite.common.features.timeseries_lags.build_autoreg_df_multi`.
          The keys of the dictionary are the target lagged regressor column names.
          It can leverage the regressors included in ``df``.
          The value of each key is either a `dict` or `str`.
          If `dict`, it has the following keys:
  
              ``"lag_dict"`` : `dict` or None
              ``"agg_lag_dict"`` : `dict` or None
              ``"series_na_fill_func"`` : callable
  
          If `str`, it represents a method and a dictionary will be constructed using that `str`.
          Currently the only implemented method is "auto" which uses
          `~greykite.algo.forecast.silverkite.SilverkiteForecast.__get_default_lagged_regressor_dict`
          to create a dictionary for each lagged regressor.
          An example::
  
              lagged_regressor_dict = {
                  "regressor1": {
                      "lag_dict": {"orders": [1, 2, 3]},
                      "agg_lag_dict": {
                          "orders_list": [[7, 7 * 2, 7 * 3]],
                          "interval_list": [(8, 7 * 2)]},
                      "series_na_fill_func": lambda s: s.bfill().ffill()},
                  "regressor2": "auto"}
  
          Check the docstring of `~greykite.common.features.timeseries_lags.build_autoreg_df_multi`
          for more details for each argument.
      changepoints_dict : `dict` or None, optional, default None
          Specifies the changepoint configuration.
  
          "method": `str`
              The method to locate changepoints.
              Valid options:
  
                  - "uniform". Places n_changepoints evenly spaced changepoints to allow growth to change.
                  - "custom". Places changepoints at the specified dates.
                  - "auto". Automatically detects change points. For configuration, see
                    `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_trend_changepoints`
  
              Additional keys to provide parameters for each particular method are described below.
          "continuous_time_col": `str`, optional
              Column to apply ``growth_func`` to, to generate changepoint features
              Typically, this should match the growth term in the model
          "growth_func": Optional[func]
              Growth function (scalar -> scalar). Changepoint features are created
              by applying ``growth_func`` to ``continuous_time_col`` with offsets.
              If None, uses identity function to use ``continuous_time_col`` directly
              as growth term
              If changepoints_dict["method"] == "uniform", this other key is required:
  
                  ``"n_changepoints"``: int
                      number of changepoints to evenly space across training period
  
              If changepoints_dict["method"] == "custom", this other key is required:
  
                  ``"dates"``: Iterable[Union[int, float, str, datetime]]
                      Changepoint dates. Must be parsable by pd.to_datetime.
                      Changepoints are set at the closest time on or after these dates
                      in the dataset.
  
              If changepoints_dict["method"] == "auto", the keys that matches the parameters in
              `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_trend_changepoints`,
              except ``df``, ``time_col`` and ``value_col``, are optional.
              Extra keys also include "dates", "combine_changepoint_min_distance" and "keep_detected" to specify
              additional custom trend changepoints. These three parameters correspond to the three parameters
              "custom_changepoint_dates", "min_distance" and "keep_detected" in
              `~greykite.algo.changepoint.adalasso.changepoints_utils.combine_detected_and_custom_trend_changepoints`.
  
      seasonality_changepoints_dict : `dict` or None, default `None`
          The parameter dictionary for seasonality change point detection. Parameters are in
          `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_seasonality_changepoints`.
          Note ``df``, ``time_col``, ``value_col`` and ``trend_changepoints`` are auto populated,
          and do not need to be provided.
      changepoint_detector : `ChangepointDetector` or `None`, default `None`
          The ChangepointDetector class
          :class:`~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector`.
          This is specifically for
          `~greykite.algo.forecast.silverkite.forecast_simple_silverkite.forecast_simple_silverkite`
          to pass the ChangepointDetector class for plotting purposes, in case that users use
          ``forecast_simple_silverkite`` with ``changepoints_dict["method"] == "auto"``. The
          trend change point detection has to be run there to include possible interaction terms,
          so we need to pass the detection result from there to include in the output.
      min_admissible_value : `float` or None, optional, default `None`
          The minimum admissible value to return during prediction.
          If None, no limit is applied.
      max_admissible_value : `float` or None, optional, default `None`
          The maximum admissible value to return during prediction.
          If None, no limit is applied.
      uncertainty_dict : `dict` or None, optional, default `None`
          How to fit the uncertainty model. A dictionary with keys:
              ``"uncertainty_method"`` : `str`
                  The title of the method.
                  Only "simple_conditional_residuals" is implemented
                  in ``fit_ml_model`` which calculates CIs using residuals
              ``"params"`` : `dict`
                  A dictionary of parameters needed for
                  the requested ``uncertainty_method``. For example, for
                  ``uncertainty_method="simple_conditional_residuals"``, see
                  parameters of `~greykite.algo.uncertainty.conditional.conf_interval.conf_interval`:
  
                      ``"conditional_cols"``
                      ``"quantiles"``
                      ``"quantile_estimation_method"``
                      ``"sample_size_thresh"``
                      ``"small_sample_size_method"``
                      ``"small_sample_size_quantile"``
  
          If None, no uncertainty intervals are calculated.
      normalize_method : `str` or None, default None
          If a string is provided, it will be used as the normalization method
          in `~greykite.common.features.normalize.normalize_df`, passed via
          the argument ``method``.
          Available options are: "zero_to_one", "statistical", "minus_half_to_half", "zero_at_origin".
          If None, no normalization will be performed.
          See that function for more details.
      adjust_anomalous_dict : `dict` or None, default None
          If not None, a dictionary with following items:
  
              - "func" : `callable`
                  A function to perform adjustment of anomalous data with following
                  signature::
  
                      adjust_anomalous_dict["func"](
                          df=df,
                          time_col=time_col,
                          value_col=value_col,
                          **params) ->
                      {"adjusted_df": adjusted_df, ...}
  
              - "params" : `dict`
                  The extra parameters to be passed to the function above.
      impute_dict : `dict` or None, default None
          If not None, a dictionary with following items:
  
          - "func" : `callable`
              A function to perform imputations with following
              signature::
  
                  impute_dict["func"](
                      df=df,
                      value_col=value_col,
                      **impute_dict["params"] ->
                  {"df": imputed_df, ...}
  
          - "params" : `dict`
              The extra parameters to be passed to the function above.
  
      regression_weight_col : `str` or None, default None
          The column name for the weights to be used in weighted regression version
          of applicable machine-learning models.
      forecast_horizon : `int` or None, default None
          The number of periods for which forecast is needed.
          Note that this is only used in deciding what parameters should be
          used for certain components e.g. autoregression, if automatic methods
          are requested. While, the prediction time forecast horizon could be different
          from this variable, ideally they should be the same.
      simulation_based : `bool`, default False
          Boolean to specify if the future predictions are to be using simulations
          or not.
          Note that this is only used in deciding what parameters should be
          used for certain components e.g. autoregression, if automatic methods
          are requested. However, the auto-settings and the prediction settings
          regarding using simulations should match.
      simulation_num : `int`, default 10
          The number of simulations for when simulations are used for generating
          forecasts and prediction intervals.
      fast_simulation: `bool`, default False
          Deterimes if fast simulations are to be used. This only impacts models
          which include auto-regression. This method will only generate one simulation
          without any error being added and then add the error using the volatility
          model. The advantage is a major boost in speed during inference and the
          disadvantage is potentially less accurate prediction intervals.
      remove_intercept : `bool`, default False
          Whether to remove explicit and implicit intercepts.
          By default, `patsy` will make the design matrix always full rank.
          It will always include an intercept term unless we specify "-1" or "+0".
          However, if there are categorical variables, even we specify "-1" or "+0",
          it will include an implicit intercept by adding all levels of a categorical
          variable into the design matrix.
          Sometimes we don't want this to happen.
          Setting this parameter to True will remove both explicit and implicit intercepts.
  
      Returns
      -------
      trained_model : `dict`
          A dictionary that includes the fitted model from the function
          :func:`~greykite.algo.common.ml_models.fit_ml_model_with_evaluation`.
          The keys are:
  
              df_dropna: `pandas.DataFrame`
                  The ``df`` with NAs dropped.
              df: `pandas.DataFrame`
                  The original ``df``.
              num_training_points: `int`
                  The number of training points.
              features_df: `pandas.DataFrame`
                  The ``df`` with augmented time features.
              min_timestamp: `pandas.Timestamp`
                  The minimum timestamp in data.
              max_timestamp: `pandas.Timestamp`
                  The maximum timestamp in data.
              freq: `str`
                  The data frequency.
              inferred_freq: `str`
                  The data freqency inferred from data.
              inferred_freq_in_secs : `float`
                  The data frequency inferred from data in seconds.
              inferred_freq_in_days: `float`
                  The data frequency inferred from data in days.
              time_col: `str`
                  The time column name.
              value_col: `str`
                  The value column name.
              origin_for_time_vars: `float`
                  The first time stamp converted to a float number.
              fs_components_df: `pandas.DataFrame`
                  The dataframe that specifies the seasonality Fourier configuration.
              autoreg_dict: `dict`
                  The dictionary that specifies the autoregression configuration.
              lagged_regressor_dict: `dict`
                  The dictionary that specifies the lagged regressors configuration.
              lagged_regressor_cols: `list` [`str`]
                  List of regressor column names used for lagged regressor
              normalize_method: `str`
                  The normalization method.
                  See the function input parameter ``normalize_method``.
              daily_event_df_dict: `dict`
                  The dictionary that specifies daily events configuration.
              changepoints_dict: `dict`
                  The dictionary that specifies changepoints configuration.
              changepoint_values: `list` [`float`]
                  The list of changepoints in continuous time values.
              normalized_changepoint_values : `list` [`float`]
                  The list of changepoints in continuous time values normalized to 0 to 1.
              continuous_time_col: `str`
                  The continuous time column name in ``features_df``.
              growth_func: `func`
                  The growth function used in changepoints, None is linear function.
              fs_func: `func`
                  The function used to generate Fourier series for seasonality.
              has_autoreg_structure: `bool`
                  Whether the model has autoregression structure.
              autoreg_func: `func`
                  The function to generate autoregression columns.
              min_lag_order: `int`
                  Minimal lag order in autoregression.
              max_lag_order: `int`
                  Maximal lag order in autoregression.
              has_lagged_regressor_structure: `bool`
                  Whether the model has lagged regressor structure.
              lagged_regressor_func: `func`
                  The function to generate lagged regressor columns.
              min_lagged_regressor_order: `int`
                  Minimal lag order in lagged regressors.
              max_lagged_regressor_order: `int`
                  Maximal lag order in lagged regressors.
              uncertainty_dict: `dict`
                  The dictionary that specifies uncertainty model configuration.
              pred_cols: `list` [`str`]
                  List of predictor names.
              last_date_for_fit: `str` or `pandas.Timestamp`
                  The last timestamp used for fitting.
              trend_changepoint_dates: `list` [`pandas.Timestamp`]
                  List of trend changepoints.
              changepoint_detector: `class`
                  The `ChangepointDetector` class used to detected trend changepoints.
              seasonality_changepoint_dates: `list` [`pandas.Timestamp`]
                  List of seasonality changepoints.
              seasonality_changepoint_result: `dict`
                  The seasonality changepoint detection results.
              fit_algorithm: `str`
                  The algorithm used to fit the model.
              fit_algorithm_params: `dict`
                  The dictionary of parameters for ``fit_algorithm``.
              adjust_anomalous_info: `dict`
                  A dictionary that has anomaly adjustment results.
              impute_info: `dict`
                  A dictionary that has the imputation results.
              forecast_horizon: `int`
                  The forecast horizon in steps.
              forecast_horizon_in_days: `float`
                  The forecast horizon in days.
              forecast_horizon_in_timedelta: `datetime.timmdelta`
                  The forecast horizon in timedelta.
              simulation_based: `bool`
                  Whether to use simulation in prediction with autoregression terms.
              simulation_num : `int`, default 10
                  The number of simulations for when simulations are used for generating
                  forecasts and prediction intervals.
              train_df : `pandas.DataFrame`
                  The past dataframe used to generate AR terms.
                  It includes the concatenation of ``past_df`` and ``df`` if ``past_df`` is provided,
                  otherwise it is the ``df`` itself.
              drop_intercept_col : `str` or None
                  The intercept column, explicit or implicit, to be dropped.
  
      """
      df = df.copy()
      df[time_col] = pd.to_datetime(df[time_col])
      num_training_points = df.shape[0]
      adjust_anomalous_info = None
  
      if simulation_num is not None:
          assert (
              simulation_num > 0
          ), "simulation number must be a natural number"
  
      if past_df is not None:
          if past_df.shape[0] == 0:
              past_df = None
          else:
              past_df = past_df.copy()
              past_df[time_col] = pd.to_datetime(past_df[time_col])
              past_df = past_df.sort_values(by=time_col)
  
      # Adjusts anomalies if requested
      if adjust_anomalous_dict is not None:
          adjust_anomalous_info = adjust_anomalous_dict["func"](
              df=df,
              time_col=time_col,
              value_col=value_col,
              **adjust_anomalous_dict["params"],
          )
          df = adjust_anomalous_info["adjusted_df"]
  
      impute_info = None
      if impute_dict is not None:
          impute_info = impute_dict["func"](
              df=df, value_col=value_col, **impute_dict["params"]
          )
          df = impute_info["df"]
  
      # Calculates time properties of the series
      # We include these properties in the returned `trained_model` object
      time_stats = describe_timeseries(df, time_col=time_col)
      max_timestamp = time_stats["max_timestamp"]
      min_timestamp = time_stats["min_timestamp"]
      # This infers a constant length freq (in seconds units or days) from data.
      # Note that in some cases e.g. monthly or annual data the real frequency can
      # be non-constant in length.
      # This inferred freq is of `pandas._libs.tslibs.timedeltas.Timedelta` type.
      inferred_freq = time_stats["freq_in_timedelta"]
      inferred_freq_in_secs = time_stats["freq_in_secs"]
      inferred_freq_in_days = time_stats["freq_in_days"]
      # However in some use cases user might provide more complex
      # freq to `predict_n` functions.
      # As an example `freq='W-SUN'` which can be passed by user.
      # If such freq is not passed, we can attempt to infer it.
      # Note that if there are data with gaps, this freq cannot be inferred.
      # E.g. if hourly data only include 9am-5pm.
      if freq is None:
          freq = pd.infer_freq(df[time_col])
  
      # Calculates forecast horizon (as a number of observations)
      if forecast_horizon is None:
          # expected to be kept in sync with default value set in ``get_default_time_parameters``
          forecast_horizon = get_default_horizon_from_period(
              period=inferred_freq_in_secs,
              num_observations=num_training_points,
          )
      forecast_horizon_in_timedelta = inferred_freq * forecast_horizon
      forecast_horizon_in_days = (
          inferred_freq_in_days * forecast_horizon
      )
  
      if extra_pred_cols is None:
          extra_pred_cols = [
              TimeFeaturesEnum.ct1.value
          ]  # linear in time
  
      # Makes sure the ``train_test_thresh`` is within the data
      last_time_available = max(df[time_col])
      if (
          train_test_thresh is not None
          and train_test_thresh >= last_time_available
      ):
          raise ValueError(
              f"Input timestamp for the parameter 'train_test_threshold' "
              f"({train_test_thresh}) exceeds the maximum available timestamp "
              f"of the time series ({last_time_available})."
              f"Please pass a value within the range."
          )
  
      # Sets default origin so that "ct1" feature from `build_time_features_df`
      # Starts at 0 on train start date
      if origin_for_time_vars is None:
          origin_for_time_vars = get_default_origin_for_time_vars(
              df, time_col
          )
      # Updates `changepoints_dict`, unchanged if not "method" == "auto"
      (
          changepoints_dict,
          changepoint_detector_class,
      ) = get_changepoints_dict(
          df=df,
          time_col=time_col,
          value_col=value_col,
          changepoints_dict=changepoints_dict,
      )
      if changepoint_detector_class is None:
          # Handles the case that user uses `forecast_simple_silverkite` with automatic
          # trend change point detection. In that case, the `changepoints_dict` is already
          # transformed to "method" = "custom", thus no changepoint detector is returned
          # by `get_changepoints_dict`, so we need the original `ChangepointDetector` class
          # to include in the output for plotting purpose.
          changepoint_detector_class = changepoint_detector
      # Defines trend changepoints.
      # `df` contains all dates in the training period, including those
      # where `value_col` is np.nan and therefore not used in training
      # by `fit_ml_model_with_evaluation`.
      # Thus, when changepoint "method" = "uniform", all dates are used to uniformly
      # place the changepoints. When changepoint "method" = "auto", only dates without
      # missing values are used to place potential changepoints, after resampling
      # according to `resample_freq`. Seasonality changepoints are also placed using
      # resampled dates after excluding the missing values.
      trend_changepoint_dates = (
          get_changepoint_dates_from_changepoints_dict(
              changepoints_dict=changepoints_dict,
              df=df,
              time_col=time_col,
          )
      )
      changepoints = get_changepoint_features_and_values_from_config(
          df=df,
          time_col=time_col,
          changepoints_dict=changepoints_dict,
          origin_for_time_vars=origin_for_time_vars,
      )
      # Checks the provided `extra_pred_cols`. If it contains a feature involving a changepoint,
      # the changepoint must be valid
      keep_extra_pred_cols = []
      for col in extra_pred_cols:
          if CHANGEPOINT_COL_PREFIX in col:
              for changepoint_col in changepoints["changepoint_cols"]:
                  if changepoint_col in col:
                      keep_extra_pred_cols.append(col)
                      break
          else:
              keep_extra_pred_cols.append(col)
      if len(keep_extra_pred_cols) < len(extra_pred_cols):
          removed_pred_cols = set(extra_pred_cols) - set(
              keep_extra_pred_cols
          )
          extra_pred_cols = keep_extra_pred_cols
          warnings.warn(
              f"The following features in extra_pred_cols are removed for this"
              f" training set: {removed_pred_cols}. This is possible if running backtest"
              f" or cross validation, but you are fitting on the entire training set,"
              f" double check `extra_pred_cols` and other configuration."
          )
  
      changepoint_values = changepoints["changepoint_values"]
      continuous_time_col = changepoints["continuous_time_col"]
      changepoint_cols = changepoints["changepoint_cols"]
      growth_func = changepoints["growth_func"]
  
      # Adds fourier series for seasonality
      # Initializes fourier series function with None
      # and alters if fourier components are input
      fs_func = None
      fs_cols = []
      if fs_components_df is not None:
          fs_components_df = fs_components_df[
              fs_components_df["order"] != 0
          ]
          fs_components_df = fs_components_df.reset_index()
          if len(fs_components_df.index) > 0:
              fs_func = fourier_series_multi_fcn(
                  col_names=fs_components_df[
                      "name"
                  ],  # looks for corresponding column name in input df
                  periods=fs_components_df.get("period"),
                  orders=fs_components_df.get("order"),
                  seas_names=fs_components_df.get("seas_names"),
              )
              # Determines fourier series column names for use in "build_features"
              fs_cols = get_fourier_feature_col_names(
                  df=df,
                  time_col=time_col,
                  fs_func=fs_func,
                  conti_year_origin=origin_for_time_vars,
              )
      # Removes fs_cols with perfect or almost perfect collinearity for OLS.
      # For example, yearly seasonality with order 4 and quarterly seasonality with order 1, and etc.
      if fit_algorithm in [
          "linear",
          "statsmodels_wls",
          "statsmodels_gls",
      ]:
          # Removes fourier columns with perfect or almost perfect collinearity.
          fs_cols = self.__remove_fourier_col_with_collinearity(fs_cols)
          # Also removes these terms from interactions.
          extra_pred_cols = self.__remove_fourier_col_with_collinearity_and_interaction(
              extra_pred_cols, fs_cols
          )
  
      # Adds seasonality change point features
      seasonality_changepoint_result = None
      seasonality_changepoints = None
      seasonality_changepoint_cols = []
      if seasonality_changepoints_dict is not None:
          seasonality_changepoint_result = get_seasonality_changepoints(
              df=df,
              time_col=time_col,
              value_col=value_col,
              trend_changepoint_dates=trend_changepoint_dates,
              seasonality_changepoints_dict=seasonality_changepoints_dict,
          )
          seasonality_changepoints = seasonality_changepoint_result[
              "seasonality_changepoints"
          ]
          seasonality_available = list(
              set([x.split("_")[-1] for x in fs_cols])
          )
          seasonality_changepoint_cols = get_seasonality_changepoint_df_cols(
              df=df,
              time_col=time_col,
              seasonality_changepoints=seasonality_changepoints,
              seasonality_components_df=seasonality_changepoint_result[
                  "seasonality_components_df"
              ],
              include_original_block=False,
              include_components=seasonality_available,
          )
  
      features_df = self.__build_silverkite_features(
          df=df,
          time_col=time_col,
          origin_for_time_vars=origin_for_time_vars,
          daily_event_df_dict=daily_event_df_dict,
          daily_event_neighbor_impact=daily_event_neighbor_impact,
          daily_event_shifted_effect=daily_event_shifted_effect,
          changepoint_values=changepoint_values,
          continuous_time_col=continuous_time_col,
          growth_func=growth_func,
          fs_func=fs_func,
          seasonality_changepoint_result=seasonality_changepoint_result,
          changepoint_dates=trend_changepoint_dates,
      )
  
      # Adds autoregression columns to feature matrix
      autoreg_func = None
      lag_col_names = []
      agg_lag_col_names = []
      min_lag_order = None
      max_lag_order = None
      if autoreg_dict is not None and isinstance(autoreg_dict, str):
          if autoreg_dict.lower() == "auto":
              autoreg_info = self.__get_default_autoreg_dict(
                  freq_in_days=inferred_freq_in_days,
                  forecast_horizon=forecast_horizon,
                  simulation_based=simulation_based,
              )
              autoreg_dict = autoreg_info["autoreg_dict"]
          else:
              raise ValueError(
                  f"The method {autoreg_dict} is not implemented."
              )
  
      has_autoreg_structure = False
      if autoreg_dict is not None:
          has_autoreg_structure = True
          autoreg_components = build_autoreg_df(
              value_col=value_col, **autoreg_dict
          )
          autoreg_func = autoreg_components["build_lags_func"]
          lag_col_names = autoreg_components["lag_col_names"]
          agg_lag_col_names = autoreg_components["agg_lag_col_names"]
          min_lag_order = autoreg_components["min_order"]
          max_lag_order = autoreg_components["max_order"]
  
          if autoreg_func is not None:
              if past_df is not None:
                  # Fills in the gaps for imputation.
                  expected_last_timestamp = df[
                      time_col
                  ].min() - to_offset(freq)
                  if (
                      past_df[time_col].iloc[-1]
                      < expected_last_timestamp
                  ):  # ``past_df`` is already sorted.
                      # If ``past_df`` and ``df`` have gap in between, adds the last timestamp before ``df``.
                      # Then the rest will be filled with NA.
                      log_message(
                          message="There is gaps between ``past_df`` and ``df``. "
                          "Filling the missing timestamps.",
                          level=LoggingLevelEnum.DEBUG,
                      )
                      last_timestamp_df = pd.DataFrame(
                          {
                              col: [np.nan]
                              if col != time_col
                              else [expected_last_timestamp]
                              for col in past_df.columns
                          }
                      )
                      past_df = past_df.append(
                          last_timestamp_df
                      ).reset_index(drop=True)
                  past_df = fill_missing_dates(
                      df=past_df, time_col=time_col, freq=freq
                  )[
                      0
                  ]  # `fill_missing_dates` returns a tuple where the first one is the df.
                  # Only takes ``past_df`` that are before ``df``.
                  past_df = past_df[
                      past_df[time_col] <= expected_last_timestamp
                  ]
              autoreg_df = self.__build_autoreg_features(
                  df=df,
                  value_col=value_col,
                  autoreg_func=autoreg_func,
                  phase="fit",
                  past_df=past_df,
              )
              features_df = pd.concat(
                  [features_df, autoreg_df], axis=1, sort=False
              )
  
      # Adds lagged regressor columns to feature matrix
      lagged_regressor_func = None
      lagged_regressor_col_names = []
      lagged_regressor_cols = []
      min_lagged_regressor_order = None
      max_lagged_regressor_order = None
      if lagged_regressor_dict is not None:
          key_remove = []
          for key, value in lagged_regressor_dict.items():
              if isinstance(value, str):
                  if value.lower() != "auto":
                      raise ValueError(
                          f"The method {value} is not implemented."
                      )
                  lag_reg_dict_info = (
                      self.__get_default_lagged_regressor_dict(
                          freq_in_days=inferred_freq_in_days,
                          forecast_horizon=forecast_horizon,
                      )
                  )
                  # If "auto" determines that no lag is needed, remove the key
                  if lag_reg_dict_info["lag_reg_dict"] is None:
                      key_remove += [key]
                  else:
                      lagged_regressor_dict[key] = lag_reg_dict_info[
                          "lag_reg_dict"
                      ]
          for key in key_remove:
              lagged_regressor_dict.pop(key, None)
              log_message(
                  f"Column {key} has been dropped from `lagged_regressor_dict` and was not "
                  f"used for lagged regressor as determined by 'auto' option.",
                  LoggingLevelEnum.INFO,
              )
      # Converts empty dictionary to None if all keys are removed
      if lagged_regressor_dict == {}:
          lagged_regressor_dict = None
  
      has_lagged_regressor_structure = False
      if lagged_regressor_dict is not None:
          has_lagged_regressor_structure = True
          lagged_regressor_components = build_autoreg_df_multi(
              value_lag_info_dict=lagged_regressor_dict
          )
          lagged_regressor_func = lagged_regressor_components[
              "autoreg_func"
          ]
          lagged_regressor_col_names = lagged_regressor_components[
              "autoreg_col_names"
          ]
          lagged_regressor_cols = lagged_regressor_components[
              "autoreg_orig_col_names"
          ]
          min_lagged_regressor_order = lagged_regressor_components[
              "min_order"
          ]
          max_lagged_regressor_order = lagged_regressor_components[
              "max_order"
          ]
  
          lagged_regressor_df = self.__build_lagged_regressor_features(
              df=df,
              lagged_regressor_cols=lagged_regressor_cols,
              lagged_regressor_func=lagged_regressor_func,
              phase="fit",
              past_df=None,
          )
          features_df = pd.concat(
              [features_df, lagged_regressor_df], axis=1, sort=False
          )
  
      features_df[value_col] = df[value_col].values
  
      # prediction cols
      # (Includes growth, interactions, if specified in extra_pred_cols)
      pred_cols = extra_pred_cols + fs_cols
      if changepoint_cols is not None:
          pred_cols = pred_cols + changepoint_cols
      if seasonality_changepoint_cols:
          pred_cols = pred_cols + seasonality_changepoint_cols
      if lag_col_names is not None:
          pred_cols = pred_cols + lag_col_names
      if agg_lag_col_names is not None:
          pred_cols = pred_cols + agg_lag_col_names
      if lagged_regressor_col_names is not None:
          pred_cols = pred_cols + lagged_regressor_col_names
  
      pred_cols = unique_elements_in_list(pred_cols)
      # Drops un-desired predictors
      if drop_pred_cols is not None:
          pred_cols = [
              col for col in pred_cols if col not in drop_pred_cols
          ]
  
      # Only uses predictors appearing in ``explicit_pred_cols``
      if explicit_pred_cols is not None:
          pred_cols = explicit_pred_cols
  
      # Makes sure we don't have an empty regressor string, which will cause patsy formula error.
      if not pred_cols:
          pred_cols = ["1"]
      explan_str = "+".join(pred_cols)
      model_formula_str = value_col + "~" + explan_str
      ind_train = None
      ind_test = None
  
      if train_test_thresh is not None:
          ind_train = np.where(df[time_col] < train_test_thresh)[
              0
          ].tolist()
          ind_test = np.where(df[time_col] >= train_test_thresh)[
              0
          ].tolist()
  
      trained_model = fit_ml_model_with_evaluation(
          df=features_df,
          model_formula_str=model_formula_str,
          fit_algorithm=fit_algorithm,
          fit_algorithm_params=fit_algorithm_params,
          ind_train=ind_train,
          ind_test=ind_test,
          training_fraction=training_fraction,
          randomize_training=False,
          min_admissible_value=min_admissible_value,
          max_admissible_value=max_admissible_value,
          uncertainty_dict=uncertainty_dict,
          normalize_method=normalize_method,
          regression_weight_col=regression_weight_col,
          remove_intercept=remove_intercept,
      )
  
      # Normalizes the changepoint_values
      normalized_changepoint_values = (
          self.__normalize_changepoint_values(
              changepoint_values=changepoint_values,
              pred_cols=pred_cols,
              continuous_time_col=continuous_time_col,
              normalize_df_func=trained_model["normalize_df_func"],
          )
      )
  
      # Excludes points with NA that are not used in fitting, similar to "y" and "x_mat".
      trained_model["df_dropna"] = df.loc[trained_model["y"].index]
      # Includes points with NA
      trained_model["df"] = df
      trained_model["num_training_points"] = num_training_points
      trained_model["features_df"] = features_df
      trained_model["min_timestamp"] = min_timestamp
      trained_model["max_timestamp"] = max_timestamp
      trained_model["freq"] = freq
      trained_model["inferred_freq"] = inferred_freq
      trained_model["inferred_freq_in_secs"] = inferred_freq_in_secs
      trained_model["inferred_freq_in_days"] = inferred_freq_in_days
      trained_model["time_col"] = time_col
      trained_model["value_col"] = value_col
      trained_model["origin_for_time_vars"] = origin_for_time_vars
      trained_model["fs_components_df"] = fs_components_df
      trained_model["autoreg_dict"] = autoreg_dict
      trained_model["lagged_regressor_dict"] = lagged_regressor_dict
      trained_model["lagged_regressor_cols"] = lagged_regressor_cols
      trained_model["normalize_method"] = normalize_method
      trained_model["daily_event_df_dict"] = daily_event_df_dict
      trained_model[
          "daily_event_neighbor_impact"
      ] = daily_event_neighbor_impact
      trained_model[
          "daily_event_shifted_effect"
      ] = daily_event_shifted_effect
      trained_model["changepoints_dict"] = changepoints_dict
      trained_model["changepoint_values"] = changepoint_values
      trained_model[
          "normalized_changepoint_values"
      ] = normalized_changepoint_values
      trained_model["continuous_time_col"] = continuous_time_col
      trained_model["growth_func"] = growth_func
      trained_model["fs_func"] = fs_func
      trained_model["has_autoreg_structure"] = has_autoreg_structure
      trained_model["autoreg_func"] = autoreg_func
      # ``past_df`` has been manipulated to have all timestamps (could be with NA) and immediately
      # precedes ``df``. If ``past_df`` is not None, the stored ``past_df`` will be the concatenation of
      # ``past_df`` and ``df``. Otherwise it will be ``df``.
      trained_model["train_df"] = pd.concat(
          [past_df, df], axis=0
      ).reset_index(drop=True)
      trained_model["min_lag_order"] = min_lag_order
      trained_model["max_lag_order"] = max_lag_order
      trained_model[
          "has_lagged_regressor_structure"
      ] = has_lagged_regressor_structure
      trained_model["lagged_regressor_func"] = lagged_regressor_func
      trained_model[
          "min_lagged_regressor_order"
      ] = min_lagged_regressor_order
      trained_model[
          "max_lagged_regressor_order"
      ] = max_lagged_regressor_order
      trained_model["uncertainty_dict"] = uncertainty_dict
      trained_model["pred_cols"] = pred_cols  # predictor column names
      trained_model["last_date_for_fit"] = max(df[time_col])
      trained_model["trend_changepoint_dates"] = trend_changepoint_dates
      trained_model[
          "changepoint_detector"
      ] = changepoint_detector_class  # the ChangepointDetector class with detection results
      trained_model[
          "seasonality_changepoint_dates"
      ] = seasonality_changepoints
      trained_model[
          "seasonality_changepoint_result"
      ] = seasonality_changepoint_result
      trained_model["fit_algorithm"] = fit_algorithm
      trained_model["fit_algorithm_params"] = fit_algorithm_params
      trained_model["adjust_anomalous_info"] = adjust_anomalous_info
      trained_model["impute_info"] = impute_info
      trained_model["forecast_horizon"] = forecast_horizon
      trained_model[
          "forecast_horizon_in_days"
      ] = forecast_horizon_in_days
      trained_model[
          "forecast_horizon_in_timedelta"
      ] = forecast_horizon_in_timedelta
      trained_model["simulation_based"] = simulation_based
      trained_model["simulation_num"] = simulation_num
      trained_model["fast_simulation"] = fast_simulation
  
      return trained_model
  
imports: import re
  import warnings
  from typing import Type
  import numpy
  import pandas
  from pandas.plotting import register_matplotlib_converters
  from pandas.tseries.frequencies import to_offset
  from greykite.algo.changepoint.adalasso.changepoint_detector import get_changepoints_dict
  from greykite.algo.changepoint.adalasso.changepoint_detector import get_seasonality_changepoints
  from greykite.algo.changepoint.adalasso.changepoints_utils import build_seasonality_feature_df_from_detection_result
  from greykite.algo.changepoint.adalasso.changepoints_utils import get_seasonality_changepoint_df_cols
  from greykite.algo.common.ml_models import fit_ml_model_with_evaluation
  from greykite.algo.common.ml_models import predict_ml
  from greykite.algo.common.ml_models import predict_ml_with_uncertainty
  from greykite.algo.forecast.silverkite.constants.silverkite_constant import default_silverkite_constant
  from greykite.algo.forecast.silverkite.constants.silverkite_seasonality import SilverkiteSeasonalityEnum
  from greykite.algo.forecast.silverkite.constants.silverkite_seasonality import SilverkiteSeasonalityEnumMixin
  from greykite.algo.forecast.silverkite.forecast_silverkite_helper import get_fourier_feature_col_names
  from greykite.algo.forecast.silverkite.forecast_silverkite_helper import get_similar_lag
  from greykite.common.constants import CHANGEPOINT_COL_PREFIX
  from greykite.common.constants import ERR_STD_COL
  from greykite.common.constants import QUANTILE_SUMMARY_COL
  from greykite.common.constants import SEASONALITY_REGEX
  from greykite.common.constants import TimeFeaturesEnum
  from greykite.common.enums import TimeEnum
  from greykite.common.features.timeseries_features import add_daily_events
  from greykite.common.features.timeseries_features import add_time_features_df
  from greykite.common.features.timeseries_features import fourier_series_multi_fcn
  from greykite.common.features.timeseries_features import get_changepoint_dates_from_changepoints_dict
  from greykite.common.features.timeseries_features import get_changepoint_features
  from greykite.common.features.timeseries_features import get_changepoint_features_and_values_from_config
  from greykite.common.features.timeseries_features import get_default_origin_for_time_vars
  from greykite.common.features.timeseries_lags import build_autoreg_df
  from greykite.common.features.timeseries_lags import build_autoreg_df_multi
  from greykite.common.logging import LoggingLevelEnum
  from greykite.common.logging import log_message
  from greykite.common.python_utils import get_pattern_cols
  from greykite.common.python_utils import unique_elements_in_list
  from greykite.common.time_properties import describe_timeseries
  from greykite.common.time_properties import fill_missing_dates
  from greykite.common.time_properties import min_gap_in_seconds
  from greykite.common.time_properties_forecast import get_default_horizon_from_period
label_desc: Use image node_id 2 for calling the SilverkiteForecast obj's underlying member method code with example usage: obj.forecast(df, time_col, value_col, freq, origin_for_time_vars, extra_pred_cols, drop_pred_cols, explicit_pred_cols, train_test_thresh, training_fraction, fit_algorithm, fit_algorithm_params, daily_event_df_dict, daily_event_neighbor_impact, daily_event_shifted_effect, fs_components_df, autoreg_dict, past_df, lagged_regressor_dict, changepoints_dict, seasonality_changepoints_dict, changepoint_detector, min_admissible_value, max_admissible_value, uncertainty_dict, normalize_method, adjust_anomalous_dict, impute_dict, regression_weight_col, forecast_horizon, simulation_based, simulation_num, fast_simulation, remove_intercept) and returns: trained_model