def test_historical_forecasts(self, config):
        """Tests historical forecasts with retraining for expected forecast lengths and times"""
        forecast_horizon = 8
        # if no fit and retrain=false, should fit at first iteration
        model_cls, kwargs, model_kwarg, bounds = config
        model = model_cls(**kwargs, **model_kwarg)
        # set train length to be the minimum required training length
        # +1 as sklearn models require min 2 train samples
        train_length = bounds[0] + bounds[1] + 1

        if model.output_chunk_shift > 0:
            with pytest.raises(ValueError) as err:
                forecasts = model.historical_forecasts(
                    series=self.ts_pass_val,
                    forecast_horizon=forecast_horizon,
                    stride=1,
                    train_length=train_length,
                    retrain=True,
                    overlap_end=False,
                )
            assert str(err.value).startswith(
                "Cannot perform auto-regression `(n > output_chunk_length)`"
            )
            # continue the test without auto-regression if we are using shifts
            forecast_horizon = model.output_chunk_length

        # time index without train length
        forecasts_no_train_length = model.historical_forecasts(
            series=self.ts_pass_val,
            forecast_horizon=forecast_horizon,
            stride=1,
            train_length=None,
            retrain=True,
            overlap_end=False,
        )

        # time index with minimum train length
        forecasts = model.historical_forecasts(
            series=self.ts_pass_val,
            forecast_horizon=forecast_horizon,
            stride=1,
            train_length=train_length,
            retrain=True,
            overlap_end=False,
        )

        assert len(forecasts_no_train_length) == len(forecasts)
        theorical_forecast_length = (
            self.ts_val_length
            - train_length  # because we train
            - forecast_horizon  # because we have overlap_end = False
            + 1  # because we include the first element
        )
        assert len(forecasts) == theorical_forecast_length, (
            f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case "
            f"of retrain=True and overlap_end=False, and a time index of type DateTimeIndex. "
            f"Expected {theorical_forecast_length}, got {len(forecasts)}"
        )
        assert forecasts.time_index.equals(
            self.ts_pass_val.time_index[-theorical_forecast_length:]
        )

        # range index
        forecasts = model.historical_forecasts(
            series=self.ts_pass_val_range,
            forecast_horizon=forecast_horizon,
            train_length=train_length,
            stride=1,
            retrain=True,
            overlap_end=False,
        )

        assert len(forecasts) == theorical_forecast_length, (
            f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case "
            f"of retrain=True, overlap_end=False, and a time index of type RangeIndex."
            f"Expected {theorical_forecast_length}, got {len(forecasts)}"
        )
        assert forecasts.time_index.equals(
            self.ts_pass_val_range.time_index[-theorical_forecast_length:]
        )
        start_idx = self.ts_pass_val_range.get_index_at_point(forecasts.start_time())

        # stride 2
        forecasts = model.historical_forecasts(
            series=self.ts_pass_val_range,
            forecast_horizon=forecast_horizon,
            train_length=train_length,
            stride=2,
            retrain=True,
            overlap_end=False,
        )

        theorical_forecast_length = int(
            np.floor(
                (
                    (
                        self.ts_val_length
                        - train_length  # because we train
                        - forecast_horizon  # because we have overlap_end = False
                        + 1  # because we include the first element
                    )
                    - 1
                )
                / 2
                + 1  # because of stride
            )  # if odd number of elements, we keep the floor
        )

        assert len(forecasts) == theorical_forecast_length, (
            f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case "
            f"of retrain=True and overlap_end=False and stride=2. "
            f"Expected {theorical_forecast_length}, got {len(forecasts)}"
        )
        assert forecasts.time_index.equals(
            self.ts_pass_val_range.time_index[start_idx::2]
        )

        # stride 3
        forecasts = model.historical_forecasts(
            series=self.ts_pass_val_range,
            forecast_horizon=forecast_horizon,
            train_length=train_length,
            stride=3,
            retrain=True,
            overlap_end=False,
        )

        theorical_forecast_length = np.floor(
            (
                (
                    self.ts_val_length
                    - train_length  # because we train
                    - forecast_horizon  # because we have overlap_end = False
                    + 1  # because we include the first element
                )
                - 1
            )  # the first is always included, so we calculate a modulo on the rest
            / 3  # because of stride
            + 1  # and we readd the first
        )  # if odd number of elements, we keep the floor

        # Here to adapt if forecast_horizon or train_length change
        assert len(forecasts) == theorical_forecast_length, (
            f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case "
            f"of retrain=True and overlap_end=False and stride=3. "
            f"Expected {theorical_forecast_length}, got {len(forecasts)}"
        )
        assert forecasts.time_index.equals(
            self.ts_pass_val_range.time_index[start_idx::3]
        )

        # last points only False
        forecasts = model.historical_forecasts(
            series=self.ts_pass_val_range,
            forecast_horizon=forecast_horizon,
            train_length=train_length,
            stride=1,
            retrain=True,
            overlap_end=False,
            last_points_only=False,
        )

        theorical_forecast_length = (
            self.ts_val_length
            - train_length  # because we train
            - forecast_horizon  # because we have overlap_end = False
            + 1  # because we include the first element
        )

        assert len(forecasts) == theorical_forecast_length, (
            f"Model {model_cls} does not return the right number of historical forecasts in the case of "
            f"retrain=True and overlap_end=False, and last_points_only=False. "
            f"expected {theorical_forecast_length}, got {len(forecasts)}"
        )

        assert len(forecasts[0]) == forecast_horizon, (
            f"Model {model_cls} does not return forecast_horizon points per historical forecast in the case of "
            f"retrain=True and overlap_end=False, and last_points_only=False"
        )
        last_points_times = np.array([fc.end_time() for fc in forecasts])
        np.testing.assert_equal(
            last_points_times,
            self.ts_pass_val_range.time_index[-theorical_forecast_length:].values,
        )

        if not model.supports_past_covariates:
            with pytest.raises(ValueError) as msg:
                model.historical_forecasts(
                    series=self.ts_pass_val_range,
                    past_covariates=self.ts_passengers,
                    retrain=True,
                )
            assert str(msg.value).startswith(
                "Model cannot be fit/trained with `past_covariates`."
            )

        if not model.supports_future_covariates:
            with pytest.raises(ValueError) as msg:
                model.historical_forecasts(
                    series=self.ts_pass_val_range,
                    future_covariates=self.ts_passengers,
                    last_points_only=False,
                )
            assert str(msg.value).startswith(
                "Model cannot be fit/trained with `future_covariates`."
            )
Tests historical forecasts with retraining for expected forecast lengths and times
darts/tests/utils/historical_forecasts/test_historical_forecasts.py