docstr: This function takes a dataframe ``fut_df`` which includes the timestamps to forecast
  and a ``trained_model`` returned by
  `~greykite.algo.forecast.silverkite.SilverkiteForecast.forecast`
  and decomposes
  ``fut_df`` to various dataframes which reflect if the timestamps are before,
  during or after the training periods.
  It also determines if: 'the future timestamps after the training period' are immediately
  after 'the last training period' or if there is some extra gap.
  In that case, this function creates an expanded dataframe which includes the missing
  timestamps as well.
  If ``fut_df`` also includes extra columns (they could be regressor columns),
  this function will interpolate the extra regressor columns.
  
  Parameters
  ----------
  fut_df : `pandas.DataFrame`
      The data frame which includes the timestamps for prediction
      and possibly regressors. Note that the timestamp column in ``fut_df``
      must be the same as ``trained_model["time_col"]``.
      We assume ``fut_df[time_col]`` is pandas.datetime64 type.
  trained_model : `dict`
      A fitted silverkite model which is the output of
      `~greykite.algo.forecast.silverkite.SilverkiteForecast.forecast`
  freq : `str`
      Timeseries frequency, DateOffset alias.
      See https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
      for the allowed frequencies.
  na_fill_func : callable (`pd.DataFrame` -> `pd.DataFrame`)
      default::
  
          lambda df: df.interpolate().bfill().ffill()
  
      A function which interpolated missing values in a dataframe.
      The main usage is invoked when there is a gap between the timestamps.
      In that case to fill in the gaps, the regressors need to be interpolated/filled.
      The default works by first interpolating the continuous variables.
      Then it uses back-filling and then forward-filling for categorical variables.
  
  Returns
  -------
  result: `dict`
      A dictionary with following items:
  
      - ``"fut_freq_in_secs"``: `float`
          The inferred frequency in ``fut_df``
      - ``"training_freq_in_secs"``: `float`
          The inferred frequency in training data
      - ``"index_before_training"``: `list` [`bool`]
          A boolean list to determine which rows of ``fut_df`` include a time
          which is before the training start.
      - ``"index_within_training"``: `list` [`bool`]
          A boolean list to determine which rows of ``fut_df`` include a time
          which is during the training period.
      - ``"index_after_training"``: `list` [`bool`]
          A boolean list to determine which rows of ``fut_df`` include a time
          which is after the training end date.
      - ``"fut_df_before_training"``: `pandas.DataFrame`
          A partition of ``fut_df`` with timestamps before the training start date
      - ``"fut_df_within_training"``: `pandas.DataFrame`
          A partition of ``fut_df`` with timestamps during the training period
      - ``"fut_df_after_training"``: `pandas.DataFrame`
          A partition of ``fut_df`` with timestamps after the training start date
      - ``"fut_df_gap"``: `pandas.DataFrame` or None
          If there is a gap between training end date and the first timestamp
          after the training end date in ``fut_df``, this dataframe can fill the
          gap between the two. In case ``fut_df`` includes extra columns as well,
          the values for those columns will be filled using ``na_fill_func``.
      - ``"fut_df_after_training_expanded"``: `pandas.DataFrame`
          If there is a gap between training end date and the first timestamp
          after the training end date in ``fut_df``, this dataframe will include
          the data for the gaps (``fut_df_gap``) as well as ``fut_df_after_training``.
      - ``"index_after_training_original"``: `list` [`bool`]
          A boolean list to determine which rows of ``fut_df_after_training_expanded``
          correspond to raw data passed by user which are after training end date,
          appearing in ``fut_df``.
          Note that this partition corresponds to ``fut_df_after_training``
          which is the subset of data in ``fut_df`` provided by user and
          also returned by this function.
      - ``"missing_periods_num"``: `int`
          Number of missing timestamps between the last date of training and
          first date in ``fut_df`` appearing after the training end date
      - ``"inferred_forecast_horizon"``: `int`
          This is the inferred forecast horizon from ``fut_df``.
          This is defined to be the distance between the last training end date
          and last date appearing in ``fut_df``.
          Note that this value can be smaller or larger than the number of
          rows of ``fut_df``.
          This is calculated by adding the number of potentially missing timestamps
          and the number of time periods appearing after the training end point.
          Also note if there are no timestamps after the training end point in
          ``fut_df``, this value will be zero.
      - ``"forecast_partition_summary"``: `dict`
          A dictionary which includes the size of various partitions of ``fut_df``
          as well as the missing timestamps if needed. The dictionary keys are as
          follows:
  
              - ``"len_before_training"``: the number of time periods before training start
              - ``"len_within_training"``: the number of time periods within training
              - ``"len_after_training"``: the number of time periods after training
              - ``"len_gap"``: the number of missing time periods between training data and
                future time stamps in ``fut_df``
code: def partition_fut_df(
      self,
      fut_df,
      trained_model,
      freq,
      na_fill_func=lambda s: s.interpolate().bfill().ffill(),
  ):
      """This function takes a dataframe ``fut_df`` which includes the timestamps to forecast
      and a ``trained_model`` returned by
      `~greykite.algo.forecast.silverkite.SilverkiteForecast.forecast`
      and decomposes
      ``fut_df`` to various dataframes which reflect if the timestamps are before,
      during or after the training periods.
      It also determines if: 'the future timestamps after the training period' are immediately
      after 'the last training period' or if there is some extra gap.
      In that case, this function creates an expanded dataframe which includes the missing
      timestamps as well.
      If ``fut_df`` also includes extra columns (they could be regressor columns),
      this function will interpolate the extra regressor columns.
  
      Parameters
      ----------
      fut_df : `pandas.DataFrame`
          The data frame which includes the timestamps for prediction
          and possibly regressors. Note that the timestamp column in ``fut_df``
          must be the same as ``trained_model["time_col"]``.
          We assume ``fut_df[time_col]`` is pandas.datetime64 type.
      trained_model : `dict`
          A fitted silverkite model which is the output of
          `~greykite.algo.forecast.silverkite.SilverkiteForecast.forecast`
      freq : `str`
          Timeseries frequency, DateOffset alias.
          See https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
          for the allowed frequencies.
      na_fill_func : callable (`pd.DataFrame` -> `pd.DataFrame`)
          default::
  
              lambda df: df.interpolate().bfill().ffill()
  
          A function which interpolated missing values in a dataframe.
          The main usage is invoked when there is a gap between the timestamps.
          In that case to fill in the gaps, the regressors need to be interpolated/filled.
          The default works by first interpolating the continuous variables.
          Then it uses back-filling and then forward-filling for categorical variables.
  
      Returns
      -------
      result: `dict`
          A dictionary with following items:
  
          - ``"fut_freq_in_secs"``: `float`
              The inferred frequency in ``fut_df``
          - ``"training_freq_in_secs"``: `float`
              The inferred frequency in training data
          - ``"index_before_training"``: `list` [`bool`]
              A boolean list to determine which rows of ``fut_df`` include a time
              which is before the training start.
          - ``"index_within_training"``: `list` [`bool`]
              A boolean list to determine which rows of ``fut_df`` include a time
              which is during the training period.
          - ``"index_after_training"``: `list` [`bool`]
              A boolean list to determine which rows of ``fut_df`` include a time
              which is after the training end date.
          - ``"fut_df_before_training"``: `pandas.DataFrame`
              A partition of ``fut_df`` with timestamps before the training start date
          - ``"fut_df_within_training"``: `pandas.DataFrame`
              A partition of ``fut_df`` with timestamps during the training period
          - ``"fut_df_after_training"``: `pandas.DataFrame`
              A partition of ``fut_df`` with timestamps after the training start date
          - ``"fut_df_gap"``: `pandas.DataFrame` or None
              If there is a gap between training end date and the first timestamp
              after the training end date in ``fut_df``, this dataframe can fill the
              gap between the two. In case ``fut_df`` includes extra columns as well,
              the values for those columns will be filled using ``na_fill_func``.
          - ``"fut_df_after_training_expanded"``: `pandas.DataFrame`
              If there is a gap between training end date and the first timestamp
              after the training end date in ``fut_df``, this dataframe will include
              the data for the gaps (``fut_df_gap``) as well as ``fut_df_after_training``.
          - ``"index_after_training_original"``: `list` [`bool`]
              A boolean list to determine which rows of ``fut_df_after_training_expanded``
              correspond to raw data passed by user which are after training end date,
              appearing in ``fut_df``.
              Note that this partition corresponds to ``fut_df_after_training``
              which is the subset of data in ``fut_df`` provided by user and
              also returned by this function.
          - ``"missing_periods_num"``: `int`
              Number of missing timestamps between the last date of training and
              first date in ``fut_df`` appearing after the training end date
          - ``"inferred_forecast_horizon"``: `int`
              This is the inferred forecast horizon from ``fut_df``.
              This is defined to be the distance between the last training end date
              and last date appearing in ``fut_df``.
              Note that this value can be smaller or larger than the number of
              rows of ``fut_df``.
              This is calculated by adding the number of potentially missing timestamps
              and the number of time periods appearing after the training end point.
              Also note if there are no timestamps after the training end point in
              ``fut_df``, this value will be zero.
          - ``"forecast_partition_summary"``: `dict`
              A dictionary which includes the size of various partitions of ``fut_df``
              as well as the missing timestamps if needed. The dictionary keys are as
              follows:
  
                  - ``"len_before_training"``: the number of time periods before training start
                  - ``"len_within_training"``: the number of time periods within training
                  - ``"len_after_training"``: the number of time periods after training
                  - ``"len_gap"``: the number of missing time periods between training data and
                    future time stamps in ``fut_df``
  
      """
      fut_df = fut_df.copy()
      training_start_timestamp = trained_model["min_timestamp"]
      training_end_timestamp = trained_model["max_timestamp"]
      training_freq_in_secs = trained_model["inferred_freq_in_secs"]
      time_col = trained_model["time_col"]
  
      if len(fut_df) > 1:
          fut_df_time_stats = describe_timeseries(
              df=fut_df, time_col=time_col
          )
  
          if not fut_df_time_stats["regular_increments"]:
              warnings.warn(
                  "``fut_df`` does not have regular time increments"
              )
  
          if not fut_df_time_stats["increasing"]:
              raise ValueError(
                  f"``fut_df``'s time column {time_col} must be increasing in time"
              )
  
          fut_freq_in_secs = fut_df_time_stats["freq_in_secs"]
      else:
          # When test_horizon/cv_horizon/forecast_horizon is 1, not all stats above
          # are available, thus it produces an error.
          # The "else" handles this case.
          fut_freq_in_secs = None
  
      index_before_training = (
          fut_df[time_col] < training_start_timestamp
      )
      index_within_training = (
          fut_df[time_col] >= training_start_timestamp
      ) & (fut_df[time_col] <= training_end_timestamp)
      index_after_training = fut_df[time_col] > training_end_timestamp
  
      fut_df_before_training = fut_df[index_before_training]
      fut_df_within_training = fut_df[index_within_training]
      fut_df_after_training = fut_df[index_after_training]
  
      fut_df_gap = (
          None  # a dataframe which fills in the missing time periods
      )
      missing_periods_num = 0  # the number of missing time periods
  
      if fut_df_after_training.shape[0] > 0:
          min_timestamp_after_training = min(
              fut_df_after_training[time_col]
          )
          expected_timestamp_after_training = pd.date_range(
              start=training_end_timestamp, periods=2, freq=freq
          )[1]
  
          if (
              min_timestamp_after_training
              < expected_timestamp_after_training
          ):
              raise ValueError(
                  "The most immediate time in the future is off "
                  f"The last training date: {training_end_timestamp}. "
                  f"The first future period: {min_timestamp_after_training}. "
                  f"Expected first future period is {expected_timestamp_after_training}"
              )
          elif (
              min_timestamp_after_training
              > expected_timestamp_after_training
          ):
              missing_dates = pd.date_range(
                  start=expected_timestamp_after_training,
                  end=min_timestamp_after_training,
                  freq=freq,
              )
              # The last timestamp is already there, therefore we drop it
              missing_dates = missing_dates[:-1]
              missing_periods_num = len(missing_dates)
              # The length of missing dates is non-zero since there are missing timestamps
              # since ``min_timestamp_after_training > next_period_after_training``
              assert missing_periods_num > 0
              fut_df_gap = pd.DataFrame(
                  {time_col: missing_dates.tolist()}
              )
  
      # `fut_df` might include other columns than `time_col`
      # Those extra columns might be the regressors passed through `fut_df`
      # Therefore we need to ensure `fut_df_gap` includes those columns
      # Also note that those extra columns need to be imputed in that case
      if fut_df_gap is not None and len(fut_df.columns) > 1:
          fut_df_expanded = pd.concat(
              [
                  fut_df_within_training,
                  fut_df_gap,
                  fut_df_after_training,
              ],
              axis=0,
              ignore_index=True,
              sort=False,
          )
          # Imputes the missing values
          # Excludes time column which doesn't need imputation,
          # otherwise it causes error with pandas>=1.4.
          fut_df_expanded.loc[
              :, fut_df_expanded.columns != time_col
          ] = na_fill_func(
              fut_df_expanded.loc[
                  :, fut_df_expanded.columns != time_col
              ]
          )
          index = (
              [False] * fut_df_within_training.shape[0]
              + [True] * fut_df_gap.shape[0]
              + [False] * fut_df_after_training.shape[0]
          )
          fut_df_gap = fut_df_expanded[index].copy()
  
      inferred_forecast_horizon = fut_df_after_training.shape[0]
      if fut_df_gap is not None:
          inferred_forecast_horizon += fut_df_gap.shape[0]
  
      # Creates an expanded dataframe which includes the missing times
      # between the end of training and the forecast period
      fut_df_after_training_expanded = fut_df_after_training
      index_after_training_original = [
          True
      ] * fut_df_after_training.shape[0]
      if fut_df_gap is not None:
          fut_df_after_training_expanded = pd.concat(
              [fut_df_gap, fut_df_after_training],
              axis=0,
              ignore_index=True,
              sort=False,
          )
          index_after_training_original = [False] * fut_df_gap.shape[
              0
          ] + [True] * fut_df_after_training.shape[0]
  
      forecast_partition_summary = {
          "len_before_training": fut_df_before_training.shape[0],
          "len_within_training": fut_df_within_training.shape[0],
          "len_after_training": fut_df_after_training.shape[0],
          "len_gap": missing_periods_num,
      }
  
      return {
          "fut_freq_in_secs": fut_freq_in_secs,
          "training_freq_in_secs": training_freq_in_secs,
          "index_before_training": index_before_training,
          "index_within_training": index_within_training,
          "index_after_training": index_after_training,
          "fut_df_before_training": fut_df_before_training,
          "fut_df_within_training": fut_df_within_training,
          "fut_df_after_training": fut_df_after_training,
          "fut_df_gap": fut_df_gap,
          "fut_df_after_training_expanded": fut_df_after_training_expanded,
          "index_after_training_original": index_after_training_original,
          "missing_periods_num": missing_periods_num,
          "inferred_forecast_horizon": inferred_forecast_horizon,
          "forecast_partition_summary": forecast_partition_summary,
      }
  
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 12 for calling the SilverkiteForecast obj's underlying member method code with example usage: obj.partition_fut_df(fut_df, trained_model, freq, na_fill_func) and returns: dict