'In-sample and out-of-sample prediction for state space models generally
Parameters
start : int, optional
Zero-indexed observation number at which to start forecasting,
i.e., the first forecast will be at start.
end : int, optional
Zero-indexed observation number at which to end forecasting, i.e.,
the last forecast will be at end.
dynamic : int, optional
Offset relative to `start` at which to begin dynamic prediction.
Prior to this observation, true endogenous values will be used for
prediction; starting with this observation and continuing through
the end of prediction, forecasted endogenous values will be used
instead.
**kwargs
If the prediction range is outside of the sample range, any
of the state space representation matrices that are time-varying
must have updated values provided for the out-of-sample range.
For example, of `obs_intercept` is a time-varying component and
the prediction range extends 10 periods beyond the end of the
sample, a (`k_endog` x 10) matrix must be provided with the new
intercept values.
Returns
results : PredictionResults
A PredictionResults object.
Notes
All prediction is performed by applying the deterministic part of the
measurement equation using the predicted state variables.
Out-of-sample prediction first applies the Kalman filter to missing
data for the number of periods desired to obtain the predicted states.'

def predict(self, start=None, end=None, dynamic=None, **kwargs):

    if (self.memory_no_forecast or self.memory_no_predicted):
        raise ValueError('Predict    is    not    possible    if    memory    conservation    has    been    used    to    avoid    storing    forecasts    or    predicted    values.')
    if (start is None):
        start = 0
    elif (start < 0):
        raise ValueError('Cannot    predict    values    previous    to    the    sample.')
    if (end is None):
        end = self.nobs
    if (end <= start):
        raise ValueError('End    of    prediction    must    be    after    start.')
    nforecast = max(0, (end - self.nobs))
    if (dynamic is True):
        dynamic = 0
    if (dynamic is False):
        dynamic = None
    ndynamic = 0
    if (dynamic is not None):
        dynamic = (start + dynamic)
        if (dynamic < 0):
            raise ValueError('Dynamic    prediction    cannot    begin    prior    to    the    first    observation    in    the    sample.')
        elif (dynamic > end):
            warn('Dynamic    prediction    specified    to    begin    after    the    end    of    prediction,    and    so    has    no    effect.', ValueWarning)
            dynamic = None
        elif (dynamic > self.nobs):
            warn('Dynamic    prediction    specified    to    begin    during    out-of-sample    forecasting    period,    and    so    has    no    effect.', ValueWarning)
            dynamic = None
        if (dynamic is not None):
            ndynamic = max(0, (min(end, self.nobs) - dynamic))
    nstatic = (min(end, self.nobs) if (dynamic is None) else dynamic)
    representation = {}
    for (name, shape) in self.shapes.items():
        if (name == 'obs'):
            continue
        representation[name] = getattr(self, name)
    warning = 'Model    has    time-invariant    %s    matrix,    so    the    %s    argument    to    `predict`    has    been    ignored.'
    exception = 'Forecasting    for    models    with    time-varying    %s    matrix    requires    an    updated    time-varying    matrix    for    the    period    to    be    forecasted.'
    if (nforecast > 0):
        for (name, shape) in self.shapes.items():
            if (name == 'obs'):
                continue
            if (representation[name].shape[(-1)] == 1):
                if (name in kwargs):
                    warn((warning % (name, name)), ValueWarning)
            elif (name not in kwargs):
                raise ValueError((exception % name))
            else:
                mat = np.asarray(kwargs[name])
                if (len(shape) == 2):
                    validate_vector_shape(name, mat.shape, shape[0], nforecast)
                    if ((mat.ndim < 2) or (not (mat.shape[1] == nforecast))):
                        raise ValueError((exception % name))
                    representation[name] = np.c_[(representation[name], mat)]
                else:
                    validate_matrix_shape(name, mat.shape, shape[0], shape[1], nforecast)
                    if ((mat.ndim < 3) or (not (mat.shape[2] == nforecast))):
                        raise ValueError((exception % name))
                    representation[name] = np.c_[(representation[name], mat)]
    if ((ndynamic > 0) and (end < self.nobs)):
        for (name, shape) in self.shapes.items():
            if ((not (name == 'obs')) and (representation[name].shape[(-1)] > 1)):
                representation[name] = representation[name][..., :end]
    if ((ndynamic == 0) and (nforecast == 0)):
        results = self
    else:
        endog = np.empty((self.k_endog, (ndynamic + nforecast)))
        endog.fill(np.nan)
        endog = np.asfortranarray(np.c_[(self.endog[:, :nstatic], endog)])
        model_kwargs = {'filter_method': self.filter_method, 'inversion_method': self.inversion_method, 'stability_method': self.stability_method, 'conserve_memory': self.conserve_memory, 'filter_timing': self.filter_timing, 'tolerance': self.tolerance, 'loglikelihood_burn': self.loglikelihood_burn}
        model_kwargs.update(representation)
        model = KalmanFilter(endog, self.k_states, self.k_posdef, **model_kwargs)
        model.initialize_known(self.initial_state, self.initial_state_cov)
        model._initialize_filter()
        model._initialize_state()
        results = self._predict(nstatic, ndynamic, nforecast, model)
    return PredictionResults(results, start, end, nstatic, ndynamic, nforecast)
