def __init__(self, n, init_pdf, p_xt_xtp, p_yt_xt): r"""Initialise particle filter. :param int n: number of particles :param init_pdf: either :class:`~pybayes.pdfs.EmpPdf` instance that will be used directly as a posterior (and should already have initial particles sampled) or any other probability density which initial particles are sampled from :type init_pdf: :class:`~pybayes.pdfs.Pdf` :param p_xt_xtp: :math:`p(x_t|x_{t-1})` cpdf of state in *t* given state in *t-1* :type p_xt_xtp: :class:`~pybayes.pdfs.CPdf` :param p_yt_xt: :math:`p(y_t|x_t)` cpdf of observation in *t* given state in *t* :type p_yt_xt: :class:`~pybayes.pdfs.CPdf` """ if not isinstance(n, int) or n < 1: raise TypeError("n must be a positive integer") if not isinstance(init_pdf, Pdf): raise TypeError("init_pdf must be an instance ot the Pdf class") if not isinstance(p_xt_xtp, CPdf) or not isinstance(p_yt_xt, CPdf): raise TypeError("both p_xt_xtp and p_yt_xt must be instances of the CPdf class") dim = init_pdf.shape() # dimension of state if p_xt_xtp.shape() != dim or p_xt_xtp.cond_shape() < dim: raise ValueError("Expected shape() and cond_shape() of p_xt_xtp will " + "be (respectively greater than) {0}; ({1}, {2}) given.".format(dim, p_xt_xtp.shape(), p_xt_xtp.cond_shape())) self.p_xt_xtp = p_xt_xtp if p_yt_xt.cond_shape() != dim: raise ValueError("Expected cond_shape() of p_yt_xt will be {0}; {1} given." .format(dim, p_yt_xt.cond_shape())) self.p_yt_xt = p_yt_xt if isinstance(init_pdf, EmpPdf): self.emp = init_pdf # use directly else: self.emp = EmpPdf(init_pdf.samples(n))
class ParticleFilter(Filter): r"""Standard particle filter implementation with resampling. Specifying proposal density is currently unsupported, but planned; speak up if you want it! Posterior pdf is represented using :class:`~pybayes.pdfs.EmpPdf` and takes following form: .. math:: p(x_t|y_{1:t}) = \sum_{i=1}^n \omega_i \delta ( x_t - x_t^{(i)} ) """ def __init__(self, n, init_pdf, p_xt_xtp, p_yt_xt): r"""Initialise particle filter. :param int n: number of particles :param init_pdf: either :class:`~pybayes.pdfs.EmpPdf` instance that will be used directly as a posterior (and should already have initial particles sampled) or any other probability density which initial particles are sampled from :type init_pdf: :class:`~pybayes.pdfs.Pdf` :param p_xt_xtp: :math:`p(x_t|x_{t-1})` cpdf of state in *t* given state in *t-1* :type p_xt_xtp: :class:`~pybayes.pdfs.CPdf` :param p_yt_xt: :math:`p(y_t|x_t)` cpdf of observation in *t* given state in *t* :type p_yt_xt: :class:`~pybayes.pdfs.CPdf` """ if not isinstance(n, int) or n < 1: raise TypeError("n must be a positive integer") if not isinstance(init_pdf, Pdf): raise TypeError("init_pdf must be an instance ot the Pdf class") if not isinstance(p_xt_xtp, CPdf) or not isinstance(p_yt_xt, CPdf): raise TypeError("both p_xt_xtp and p_yt_xt must be instances of the CPdf class") dim = init_pdf.shape() # dimension of state if p_xt_xtp.shape() != dim or p_xt_xtp.cond_shape() < dim: raise ValueError("Expected shape() and cond_shape() of p_xt_xtp will " + "be (respectively greater than) {0}; ({1}, {2}) given.".format(dim, p_xt_xtp.shape(), p_xt_xtp.cond_shape())) self.p_xt_xtp = p_xt_xtp if p_yt_xt.cond_shape() != dim: raise ValueError("Expected cond_shape() of p_yt_xt will be {0}; {1} given." .format(dim, p_yt_xt.cond_shape())) self.p_yt_xt = p_yt_xt if isinstance(init_pdf, EmpPdf): self.emp = init_pdf # use directly else: self.emp = EmpPdf(init_pdf.samples(n)) def bayes(self, yt, cond = None): r"""Perform Bayes rule for new measurement :math:`y_t`; *cond* is ignored. :param numpy.ndarray cond: optional condition that is passed to :math:`p(x_t|x_{t-1})` after :math:`x_{t-1}` so that is can be rewritten as: :math:`p(x_t|x_{t-1}, c)`. The algorithm is as follows: 1. generate new particles: :math:`x_t^{(i)} = \text{sample from } p(x_t^{(i)}|x_{t-1}^{(i)}) \quad \forall i` 2. recompute weights: :math:`\omega_i = p(y_t|x_t^{(i)}) \omega_i \quad \forall i` 3. normalise weights 4. resample particles """ for i in range(self.emp.particles.shape[0]): # generate new ith particle: self.emp.particles[i] = self.p_xt_xtp.sample(self.emp.particles[i]) # recompute ith weight: self.emp.weights[i] *= exp(self.p_yt_xt.eval_log(yt, self.emp.particles[i])) # assure that weights are normalised self.emp.normalise_weights() # resample self.emp.resample() return True def posterior(self): return self.emp