def test_region_est_ellipsoid(self): """ Tests that region_est_ellipsoid works. """ dist = MultivariateNormalDistribution(self.MEAN, self.COV) u = ParticleDistribution( particle_locations = dist.sample(self.N_PARTICLES), particle_weights = np.ones(self.N_PARTICLES)/self.N_PARTICLES ) # ask for a confidence level of 0.5 A, c = u.region_est_ellipsoid(level=0.5) # center of ellipse should be the mean of the multinormal assert_almost_equal(np.round(c), self.MEAN, 1) # finally, the principal lengths of the ellipsoid # should be the same as COV _, QA, _ = np.linalg.svd(A) _, QC, _ = np.linalg.svd(self.COV) QA, QC = np.sqrt(QA), np.sqrt(QC) assert_almost_equal( QA / np.linalg.norm(QA), QC / np.linalg.norm(QC), 1 )
def test_est_credible_region(self): """ Tests that est_credible_region doesn't fail miserably """ dist = MultivariateNormalDistribution(self.MEAN, self.COV) u = ParticleDistribution( particle_locations = dist.sample(self.N_PARTICLES), particle_weights = np.ones(self.N_PARTICLES)/self.N_PARTICLES ) # first check that 0.95 confidence points consume 0.9 confidence points points1 = u.est_credible_region(level=0.95) points2 = u.est_credible_region(level=0.9) assert_almost_equal( np.sort(unique_rows(np.concatenate([points1, points2])), axis=0), np.sort(points1, axis=0) ) # do the same thing with different slice points1 = u.est_credible_region(level=0.95, modelparam_slice=self.SLICE) points2 = u.est_credible_region(level=0.9, modelparam_slice=self.SLICE) assert_almost_equal( np.sort(unique_rows(np.concatenate([points1, points2])), axis=0), np.sort(points1, axis=0) )
def test_region_est_hull(self): """ Tests that test_region_est_hull works """ dist = MultivariateNormalDistribution(self.MEAN, self.COV) u = ParticleDistribution( particle_locations = dist.sample(self.N_PARTICLES), particle_weights = np.ones(self.N_PARTICLES)/self.N_PARTICLES ) faces, vertices = u.region_est_hull(level=0.95) # In this multinormal case, the convex hull surface # should be centered at MEAN assert_almost_equal( np.round(np.mean(vertices, axis=0)), np.round(self.MEAN) ) # And a lower level should result in a smaller hull # and therefore smaller sample variance faces2, vertices2 = u.region_est_hull(level=0.2) assert_array_less(np.var(vertices2, axis=0), np.var(vertices, axis=0))
def test_in_credible_region(self): """ Tests that in_credible_region works. """ dist = MultivariateNormalDistribution(self.MEAN, self.COV) u = ParticleDistribution( particle_locations = dist.sample(self.N_PARTICLES), particle_weights = np.ones(self.N_PARTICLES)/self.N_PARTICLES ) # some points to test with test_points = np.random.multivariate_normal(self.MEAN, self.COV, self.N_PARTICLES) # method='pce' results = [ u.in_credible_region(test_points, level=0.9, method='pce'), u.in_credible_region(test_points, level=0.84, method='pce'), u.in_credible_region(test_points, level=0.5, method='pce'), ] assert_almost_equal( np.array([np.mean(x.astype('float')) for x in results]), np.array([0.9, 0.84, 0.5]), 3 ) # method='hpd-hull' results1 = [ u.in_credible_region(test_points, level=0.9, method='hpd-hull'), u.in_credible_region(test_points, level=0.84, method='hpd-hull'), u.in_credible_region(test_points, level=0.5, method='hpd-hull'), ] assert_array_less( np.array([0.9, 0.84, 0.5]), np.array([np.mean(x.astype('float')) for x in results1]) ) # method='hpd-mvee' results2 = [ u.in_credible_region(test_points, level=0.9, method='hpd-mvee'), u.in_credible_region(test_points, level=0.84, method='hpd-mvee'), u.in_credible_region(test_points, level=0.5, method='hpd-mvee'), ] assert_array_less( np.array([0.9, 0.84, 0.5]), np.array([np.mean(x.astype('float')) for x in results2]) ) # the mvee should be bigger than the convex hull. # this passes iff all points in the ellipses are # also in the hulls. assert_array_less( np.hstack([x.astype('float') for x in results1]), np.hstack([x.astype('float') for x in results2]) + 0.5 ) # check for no failures with slices. u.in_credible_region(test_points[:100,self.SLICE], level=0.9, method='pce', modelparam_slice=self.SLICE) u.in_credible_region(test_points[:100,self.SLICE], level=0.9, method='hpd-hull', modelparam_slice=self.SLICE) u.in_credible_region(test_points[:100,self.SLICE], level=0.9, method='hpd-mvee', modelparam_slice=self.SLICE) # check for no failures with single inputs assert(u.in_credible_region(test_points[0,:], level=0.9, method='pce').size == 1) assert(u.in_credible_region(test_points[0,:], level=0.9, method='hpd-hull').size == 1) assert(u.in_credible_region(test_points[0,:], level=0.9, method='hpd-mvee').size == 1)
def __call__(self, model, particle_dist, n_particles=None, precomputed_mean=None, precomputed_cov=None): """ Resample the particles according to algorithm given in [LW01]_. """ # Possibly recompute moments, if not provided. if precomputed_mean is None: mean = particle_dist.est_mean() else: mean = precomputed_mean if precomputed_cov is None: cov = particle_dist.est_covariance_mtx() else: cov = precomputed_cov if n_particles is None: if self._default_n_particles is None: n_particles = particle_dist.n_particles else: n_particles = self._default_n_particles # parameters in the Liu and West algorithm a, h = self._a, self._h if la.norm(cov, 'fro') == 0: # The norm of the square root of S is literally zero, such that # the error estimated in the next step will not make sense. # We fix that by adding to the covariance a tiny bit of the # identity. warnings.warn( "Covariance has zero norm; adding in small covariance in " "resampler. Consider increasing n_particles to improve covariance " "estimates.", ResamplerWarning) cov = self._zero_cov_comp * np.eye(cov.shape[0]) S, S_err = sqrtm_psd(cov) if not np.isfinite(S_err): raise ResamplerError( "Infinite error in computing the square root of the " "covariance matrix. Check that n_ess is not too small.") S = np.real(h * S) # Give shorter names to weights, locations, and nr. of random variables w = particle_dist.particle_weights l = particle_dist.particle_locations n_rvs = particle_dist.n_rvs new_locs = np.empty((n_particles, n_rvs)) cumsum_weights = np.cumsum(w) idxs_to_resample = np.arange(n_particles, dtype=int) # Loop as long as there are any particles left to resample. n_iters = 0 # Draw j with probability self.particle_weights[j]. # We do this by drawing random variates uniformly on the interval # [0, 1], then see where they belong in the CDF. js = cumsum_weights.searchsorted(np.random.random( (idxs_to_resample.size, )), side='right') # Set mu_i to a x_j + (1 - a) mu. # FIXME This should use particle_dist.particle_mean mus = a * l[js, :] + (1 - a) * mean while idxs_to_resample.size and n_iters < self._maxiter: # Keep track of how many iterations we used. n_iters += 1 # Draw x_i from N(mu_i, S). new_locs[idxs_to_resample, :] = mus + np.dot( S, self._kernel(n_rvs, mus.shape[0])).T # Now we remove from the list any valid models. # We write it out in a longer form than is strictly necessary so # that we can validate assertions as we go. This is helpful for # catching models that may not hold to the expected postconditions. resample_locs = new_locs[idxs_to_resample, :] if self._postselect: valid_mask = model.are_models_valid(resample_locs) else: valid_mask = np.ones((resample_locs.shape[0], ), dtype=bool) assert valid_mask.ndim == 1, "are_models_valid returned tensor, expected vector." n_invalid = np.sum(np.logical_not(valid_mask)) if self._debug and n_invalid > 0: logger.debug( "LW resampler found {} invalid particles; repeating.". format(n_invalid)) assert ((len(valid_mask.shape) == 1 or len(valid_mask.shape) == 2 and valid_mask.shape[-1] == 1) and valid_mask.shape[0] == resample_locs.shape[0]), ( "are_models_valid returned wrong shape {} " "for input of shape {}.").format( valid_mask.shape, resample_locs.shape) idxs_to_resample = idxs_to_resample[np.nonzero( np.logical_not(valid_mask))[0]] # This may look a little weird, but it should delete the unused # elements of js, so that we don't need to reallocate. js = js[np.logical_not(valid_mask)] mus = mus[:idxs_to_resample.size, :] if idxs_to_resample.size: # We failed to force all models to be valid within maxiter attempts. # This means that we could be propagating out invalid models, and # so we should warn about that. warnings.warn( ("Liu-West resampling failed to find valid models for {} " "particles within {} iterations.").format( idxs_to_resample.size, self._maxiter), ResamplerWarning) if self._debug: logger.debug( "LW resampling completed in {} iterations.".format(n_iters)) # Now we reset the weights to be uniform, letting the density of # particles represent the information that used to be stored in the # weights. This is done by SMCUpdater, and so we simply need to return # the new locations here. new_weights = np.ones((n_particles, )) / n_particles return ParticleDistribution(particle_locations=new_locs, particle_weights=new_weights)