Ejemplo n.º 1
0
    def __init__(self, n, init_pdf, p_bt_btp, kalman_args, kalman_class = KalmanFilter):
        r"""Initialise marginalized particle filter.

        :param int n: number of particles
        :param init_pdf: probability density which initial particles are sampled from. (both
           :math:`a_t` and :math:`b_t` parts)
        :type init_pdf: :class:`~pybayes.pdfs.Pdf`
        :param p_bt_btp: :math:`p(b_t|b_{t-1})` cpdf of the (b part of the) state in *t* given
           state in *t-1*
        :type p_bt_btp: :class:`~pybayes.pdfs.CPdf`
        :param dict kalman_args: arguments for the Kalman filter, passed as dictionary; *state_pdf*
           key should not be speficied as it is supplied by the marginalized particle filter
        :param class kalman_class: class of the filter used for the :math:`a_t` part of the system;
            defaults to :class:`KalmanFilter`
        """
        if not isinstance(n, int) or n < 1:
            raise TypeError("n must be a positive integer")
        if not isinstance(init_pdf, Pdf) or not isinstance(p_bt_btp, CPdf):
            raise TypeError("init_pdf must be a Pdf and p_bt_btp must be a CPdf")
        if not issubclass(kalman_class, KalmanFilter):
            raise TypeError("kalman_class must be a subclass (not an instance) of KalmanFilter")
        b_shape = p_bt_btp.shape()
        if p_bt_btp.cond_shape() != b_shape:
            raise ValueError("p_bt_btp's shape ({0}) and cond shape ({1}) must both be {2}".format(
                             p_bt_btp.shape(), p_bt_btp.cond_shape(), b_shape))
        self.p_bt_btp = p_bt_btp
        a_shape = init_pdf.shape() - b_shape

        # this will be removed when hardcoding Q,R into kalman filter will be removed
        kalman_args['Q'] = np.array([[-123.]])
        kalman_args['R'] = np.array([[-494658.]])

        # generate both initial parts of particles
        init_particles = init_pdf.samples(n)

        # create all Kalman filters first
        self.kalmans = np.empty(n, dtype=KalmanFilter) # array of references to Kalman filters
        gausses = np.empty(n, dtype=GaussPdf) # array of Kalman filter state pdfs
        for i in range(n):
            gausses[i] = GaussPdf(init_particles[i,0:a_shape], np.array([[1.]]))
            kalman_args['state_pdf'] = gausses[i]
            self.kalmans[i] = kalman_class(**kalman_args)
        # construct apost pdf. Important: reference to ith GaussPdf is shared between ith Kalman
        # filter's state_pdf and ith memp't gauss
        self.memp = MarginalizedEmpPdf(gausses, init_particles[:,a_shape:])
Ejemplo n.º 2
0
class MarginalizedParticleFilter(Filter):
    r"""Simple marginalized particle filter implementation. Assume that tha state vector :math:`x`
    can be divided into two parts :math:`x_t = (a_t, b_t)` and that the pdf representing the process
    model can be factorised as follows:

    .. math:: p(x_t|x_{t-1}) = p(a_t|a_{t-1}, b_t) p(b_t | b_{t-1})

    and that the :math:`a_t` part (given :math:`b_t`) can be estimated with (a subbclass of) the
    :class:`KalmanFilter`. Such system may be suitable for the marginalized particle filter, whose
    posterior pdf takes the form

    .. math::

       p &= \sum_{i=1}^n \omega_i p(a_t | y_{1:t}, b_{1:t}^{(i)}) \delta(b_t - b_t^{(i)}) \\
       p(a_t | y_{1:t}, b_{1:t}^{(i)}) &\text{ is posterior pdf of i}^{th} \text{ Kalman filter} \\
       \text{where } \quad \quad \quad \quad \quad
       b_t^{(i)} &\text{ is value of the (b part of the) i}^{th} \text{ particle} \\
       \omega_i \geq 0 &\text{ is weight of the i}^{th} \text{ particle} \quad \sum \omega_i = 1

    **Note:** currently :math:`b_t` is hard-coded to be process and observation noise covariance of the
    :math:`a_t` part. This will be changed soon and :math:`b_t` will be passed as condition to
    :meth:`KalmanFilter.bayes`.
    """

    def __init__(self, n, init_pdf, p_bt_btp, kalman_args, kalman_class = KalmanFilter):
        r"""Initialise marginalized particle filter.

        :param int n: number of particles
        :param init_pdf: probability density which initial particles are sampled from. (both
           :math:`a_t` and :math:`b_t` parts)
        :type init_pdf: :class:`~pybayes.pdfs.Pdf`
        :param p_bt_btp: :math:`p(b_t|b_{t-1})` cpdf of the (b part of the) state in *t* given
           state in *t-1*
        :type p_bt_btp: :class:`~pybayes.pdfs.CPdf`
        :param dict kalman_args: arguments for the Kalman filter, passed as dictionary; *state_pdf*
           key should not be speficied as it is supplied by the marginalized particle filter
        :param class kalman_class: class of the filter used for the :math:`a_t` part of the system;
            defaults to :class:`KalmanFilter`
        """
        if not isinstance(n, int) or n < 1:
            raise TypeError("n must be a positive integer")
        if not isinstance(init_pdf, Pdf) or not isinstance(p_bt_btp, CPdf):
            raise TypeError("init_pdf must be a Pdf and p_bt_btp must be a CPdf")
        if not issubclass(kalman_class, KalmanFilter):
            raise TypeError("kalman_class must be a subclass (not an instance) of KalmanFilter")
        b_shape = p_bt_btp.shape()
        if p_bt_btp.cond_shape() != b_shape:
            raise ValueError("p_bt_btp's shape ({0}) and cond shape ({1}) must both be {2}".format(
                             p_bt_btp.shape(), p_bt_btp.cond_shape(), b_shape))
        self.p_bt_btp = p_bt_btp
        a_shape = init_pdf.shape() - b_shape

        # this will be removed when hardcoding Q,R into kalman filter will be removed
        kalman_args['Q'] = np.array([[-123.]])
        kalman_args['R'] = np.array([[-494658.]])

        # generate both initial parts of particles
        init_particles = init_pdf.samples(n)

        # create all Kalman filters first
        self.kalmans = np.empty(n, dtype=KalmanFilter) # array of references to Kalman filters
        gausses = np.empty(n, dtype=GaussPdf) # array of Kalman filter state pdfs
        for i in range(n):
            gausses[i] = GaussPdf(init_particles[i,0:a_shape], np.array([[1.]]))
            kalman_args['state_pdf'] = gausses[i]
            self.kalmans[i] = kalman_class(**kalman_args)
        # construct apost pdf. Important: reference to ith GaussPdf is shared between ith Kalman
        # filter's state_pdf and ith memp't gauss
        self.memp = MarginalizedEmpPdf(gausses, init_particles[:,a_shape:])

    def __str__(self):
        ret = ""
        for i in range(self.kalmans.shape[0]):
            ret += "  {0}: {1:0<5.3f} * {2} {3}    kf.S: {4}\n".format(i, self.memp.weights[i],
                  self.memp.gausses[i], self.memp.particles[i], self.kalmans[i].S)
        return ret[:-1]  # trim the last newline

    def bayes(self, yt, cond = None):
        r"""Perform Bayes rule for new measurement :math:`y_t`. Uses following algorithm:

        1. generate new b parts of particles: :math:`b_t^{(i)} = \text{sample from }
           p(b_t^{(i)}|b_{t-1}^{(i)}) \quad \forall i`
        2. :math:`\text{set } Q_i = b_t^{(i)} \quad R_i = b_t^{(i)}` where :math:`Q_i, R_i` is
           covariance of process (respectively observation) noise in ith Kalman filter.
        3. perform Bayes rule for each Kalman filter using passed observation :math:`y_t`
        4. recompute weights: :math:`\omega_i = p(y_t | y_{1:t-1}, b_t^{(i)}) \omega_i` where
           :math:`p(y_t | y_{1:t-1}, b_t^{(i)})` is *evidence* (*marginal likelihood*) pdf of ith Kalman
           filter.
        5. normalise weights
        6. resample particles
        """
        for i in range(self.kalmans.shape[0]):
            # generate new b_t
            self.memp.particles[i] = self.p_bt_btp.sample(self.memp.particles[i])

            # assign b_t to kalman filter
            # TODO: more general and correct apprach would be some kind of QRKalmanFilter that would
            # accept b_t in condition. This is planned in future.
            kalman = self.kalmans[i]
            kalman.Q[0,0] = self.memp.particles[i,0]
            kalman.R[0,0] = self.memp.particles[i,0]

            kalman.bayes(yt)

            self.memp.weights[i] *= exp(kalman.evidence_log(yt))

        # make sure that weights are normalised
        self.memp.normalise_weights()
        # resample particles
        self._resample()
        return True

    def _resample(self):
        indices = self.memp.get_resample_indices()
        self.kalmans = self.kalmans[indices]  # resample kalman filters (makes references, not hard copies)
        self.memp.particles = self.memp.particles[indices]  # resample particles
        for i in range(self.kalmans.shape[0]):
            if indices[i] == i:  # copy only when needed
                continue
            self.kalmans[i] = deepcopy(self.kalmans[i])  # we need to deep copy ith kalman
            self.memp.gausses[i] = self.kalmans[i].P  # reassign reference to correct (new) state pdf

        self.memp.weights[:] = 1./self.kalmans.shape[0]  # set weights to 1/n
        return True

    def posterior(self):
        return self.memp