docstr: This function adds the prediction model features in training and
  predict phase for ``self.forecast`` internal use but can be called
  outside that context if desired.
  The features are time related features such as seasonality, change points,
  holidays, ...
  
  Parameters
  ----------
  df : `pandas.DataFrame`
      input dataframe, which could be in training phase or predict phase
  time_col : `str`
      The column name in df representing time for the time series data
      The time column values can be anything that can be parsed by pandas DatetimeIndex
  origin_for_time_vars : `float`
      The time origin used to create continuous variables for time
  daily_event_df_dict : `dict` [`str`, `pandas.DataFrame`] or None, 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.
  
      Note: 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.
  changepoint_values : `list` of Union[int, float, double]], optional
      The values of the growth term at the changepoints
      Can be generated by the ``get_evenly_spaced_changepoints``,
          `get_custom_changepoints` functions
  continuous_time_col : `str`, optional
      This parameter is used only if ``changepoint_values`` is not None.
      Column to apply growth_func to, to generate changepoint features
  growth_func: callable, optional
      Growth function (scalar -> scalar).
      This parameter is used only if ``changepoint_values`` is not None.
      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.
  fs_func: callable, optional
      A function which takes a df as input and returns an output df
      with fourier terms. ``fs_func`` is expected to be constructed using
      ``greykite.common.features.timeseries_features.fourier_series_multi_fcn``, but
      that is not a hard requirement.
  seasonality_changepoint_result: `dict`
      The detected seasonality change points result dictionary, returned by
      `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_seasonality_changepoints`.
  changepoint_dates : `list`
      List of change point dates with `strftime` attribute.
  
  Returns
  -------
  features_df : `pandas.DataFrame`
      a data frame with the added features as new columns
code: def __build_silverkite_features(
      self,
      df,
      time_col,
      origin_for_time_vars,
      daily_event_df_dict=None,
      daily_event_neighbor_impact=None,
      daily_event_shifted_effect=None,
      changepoint_values=None,
      continuous_time_col=None,
      growth_func=None,
      fs_func=None,
      seasonality_changepoint_result=None,
      changepoint_dates=None,
  ):
      """This function adds the prediction model features in training and
      predict phase for ``self.forecast`` internal use but can be called
      outside that context if desired.
      The features are time related features such as seasonality, change points,
      holidays, ...
  
      Parameters
      ----------
      df : `pandas.DataFrame`
          input dataframe, which could be in training phase or predict phase
      time_col : `str`
          The column name in df representing time for the time series data
          The time column values can be anything that can be parsed by pandas DatetimeIndex
      origin_for_time_vars : `float`
          The time origin used to create continuous variables for time
      daily_event_df_dict : `dict` [`str`, `pandas.DataFrame`] or None, 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.
  
          Note: 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.
      changepoint_values : `list` of Union[int, float, double]], optional
          The values of the growth term at the changepoints
          Can be generated by the ``get_evenly_spaced_changepoints``,
              `get_custom_changepoints` functions
      continuous_time_col : `str`, optional
          This parameter is used only if ``changepoint_values`` is not None.
          Column to apply growth_func to, to generate changepoint features
      growth_func: callable, optional
          Growth function (scalar -> scalar).
          This parameter is used only if ``changepoint_values`` is not None.
          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.
      fs_func: callable, optional
          A function which takes a df as input and returns an output df
          with fourier terms. ``fs_func`` is expected to be constructed using
          ``greykite.common.features.timeseries_features.fourier_series_multi_fcn``, but
          that is not a hard requirement.
      seasonality_changepoint_result: `dict`
          The detected seasonality change points result dictionary, returned by
          `~greykite.algo.changepoint.adalasso.changepoint_detector.ChangepointDetector.find_seasonality_changepoints`.
      changepoint_dates : `list`
          List of change point dates with `strftime` attribute.
  
      Returns
      -------
      features_df : `pandas.DataFrame`
          a data frame with the added features as new columns
      """
      # adds time features
      features_df = add_time_features_df(
          df=df,
          time_col=time_col,
          conti_year_origin=origin_for_time_vars,
      )
  
      # adds daily events (e.g. holidays)
      # if daily event data are given, we add them to temporal features data
      # ``date_col`` below is used to join with ``daily_events`` data given
      # in ``daily_event_df_dict``
      # Note: events must be provided for both train and forecast time range
      if daily_event_df_dict is not None:
          if (
              df.shape[0] > 1
              and min_gap_in_seconds(df, time_col)
              > TimeEnum.ONE_DAY_IN_SECONDS.value
          ):
              warnings.warn(
                  "The granularity of data is larger than daily. "
                  "Ensure the daily events data match the timestamps"
              )
          features_df = add_daily_events(
              df=features_df,
              event_df_dict=daily_event_df_dict,
              date_col="date",
              neighbor_impact=daily_event_neighbor_impact,
              shifted_effect=daily_event_shifted_effect,
          )
  
      # adds changepoints
      if changepoint_values is not None:
          changepoint_features_df = get_changepoint_features(
              features_df,
              changepoint_values,
              continuous_time_col=continuous_time_col,
              growth_func=growth_func,
              changepoint_dates=changepoint_dates,
          )
  
          assert (
              features_df.shape[0] == changepoint_features_df.shape[0]
          )
          features_df = pd.concat(
              [features_df, changepoint_features_df], axis=1, sort=False
          )
  
      # adds seasonality
      if fs_func is not None:
          fs_features = fs_func(features_df)
          fs_df = fs_features["df"]
          features_df = pd.concat(
              [features_df, fs_df], axis=1, sort=False
          )
  
      # adds seasonality change points
      if seasonality_changepoint_result is not None:
          seasonality_available = (
              list(set([x.split("_")[-1] for x in fs_df.columns]))
              if fs_func is not None
              else []
          )
          seasonality_df = build_seasonality_feature_df_from_detection_result(
              df=df,
              time_col=time_col,
              seasonality_changepoints=seasonality_changepoint_result[
                  "seasonality_changepoints"
              ],
              seasonality_components_df=seasonality_changepoint_result[
                  "seasonality_components_df"
              ],
              include_original_block=False,
              include_components=seasonality_available,
          )
          features_df = pd.concat(
              [features_df, seasonality_df.reset_index(drop=True)],
              axis=1,
              sort=False,
          )
      features_df.index = df.index  # assigns a copy of original index
      return features_df
  
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 13 for calling the SilverkiteForecast obj's underlying member method code with example usage: obj.__build_silverkite_features(df, time_col, origin_for_time_vars, daily_event_df_dict, daily_event_neighbor_impact, daily_event_shifted_effect, changepoint_values, continuous_time_col, growth_func, fs_func, seasonality_changepoint_result, changepoint_dates) and returns: features_df