def test_kl_divergence_between_vacuous_factors(self): """ Test that the distance between two vacuous factors is zero. """ g_1 = Gaussian.make_vacuous(var_names=["a", "b"]) g_2 = Gaussian.make_vacuous(var_names=["a", "b"]) self.assertTrue(g_1.kl_divergence(g_2) == 0.0)
def test_vacuous_equals(self): """ Test that vauous gaussians are equal. """ g_1 = Gaussian.make_vacuous(var_names=["a", "b"]) g_2 = Gaussian.make_vacuous(var_names=["a", "b"]) self.assertTrue(g_1.equals(g_2))
def test_observe(self): """ Test that the Gaussian reduce function returns the correct result. """ # Note these equations where written independently from the actuall implementation. # TODO: consider extending this test and hard-coding the expected parameters kmat = np.array([[6, 2, 1], [2, 8, 3], [1, 3, 9]]) hvec = np.array([[1], [2], [3]]) g_val = 1.5 z_observed = np.array([[6]]) expected_prec = np.array([[6, 2], [2, 8]]) expeted_h = np.array([[-5], [-16]]) expected_g = -142.5 expected_gaussian = Gaussian(prec=expected_prec, h_vec=expeted_h, g_val=expected_g, var_names=["x", "y"]) gaussian = Gaussian(prec=kmat, h_vec=hvec, g_val=g_val, var_names=["x", "y", "z"]) actual_gaussian = gaussian.reduce(vrs=["z"], values=z_observed) self.assertTrue(actual_gaussian.equals(expected_gaussian)) # Test that the result is still correct with a different parameter order. gaussian_copy = gaussian.copy() gaussian_copy._reorder_parameters(["x", "z", "y"]) actual_gaussian = gaussian_copy.reduce(vrs=["z"], values=z_observed) self.assertTrue(actual_gaussian.equals(expected_gaussian))
def test_marginalise_with_conditional(self): """ Test that marginalising an unconditioned non-linear Gaussian results in a vacuous Gaussian. """ ab_vars = ["a", "b"] cd_vars = ["c", "d"] a_mat = np.array([[2, 0], [0, 1]]) def transform(x_val, _): return a_mat.dot(x_val) noise_cov = np.array([[1, 0], [0, 1]]) nlg_factor = NonLinearGaussian(conditioning_vars=ab_vars, conditional_vars=cd_vars, transform=transform, noise_cov=noise_cov) cov = np.array([[2, 1], [1, 3]]) mean = np.array([[2], [1]]) conditional_gaussian = Gaussian(cov=cov, mean=mean, log_weight=0.0, var_names=cd_vars) nlg_factor = nlg_factor.multiply(conditional_gaussian) actual_cd_marginal = nlg_factor.marginalize(vrs=cd_vars, keep=True) expected_cd_marginal = conditional_gaussian.copy() self.assertTrue(actual_cd_marginal.equals(expected_cd_marginal))
def test_cov_exists_precision(self): """ Check that the _cov_exists function returns True for a Gaussian that has a covariance that is well defined through the precision. """ gaussian = Gaussian(prec=1.0, h_vec=0.0, g_val=0.0, var_names=["a"]) self.assertTrue(gaussian._cov_exists())
def test_get_prec(self): """ Test that the correct K is returned. """ gaussian_a = Gaussian(prec=2.0, h_vec=1.0, g_val=0.0, var_names=["a"]) self.assertTrue( np.array_equal(gaussian_a.get_prec(), np.array([[2.0]])))
def test_equals(self): """ Test that the equals function can identify Gaussians that differ only in their variables. """ gaussian_1 = Gaussian(cov=1, mean=1, log_weight=1.0, var_names=["a"]) gaussian_2 = Gaussian(cov=0, mean=1, log_weight=1.0, var_names=["b"]) self.assertFalse(gaussian_1.equals(gaussian_2))
def get_gaussian_set_a(): """ A helper function for generating a set of Gaussians for tests. """ g_1 = Gaussian(mean=[[1.0], [2.0]], cov=[[5.0, 0.0], [0.0, 4.0]], log_weight=0.0, var_names=["a", "b"]) g_2 = Gaussian(mean=[[3.0], [5.0]], cov=[[1.0, 0.0], [0.0, 2.0]], log_weight=0.0, var_names=["b", "c"]) g_3 = Gaussian(mean=[[0.0], [3.0]], cov=[[3.0, 0.0], [0.0, 4.0]], log_weight=0.0, var_names=["d", "e"]) return g_1, g_2, g_3
def test_get_log_weight_from_canform(): """ Test that the _update_covform function is called before returning the log-weight parameter. """ gaussian_a = Gaussian(prec=2.0, h_vec=1.0, g_val=0.0, var_names=["a"]) expect(Gaussian, times=1)._update_covform() gaussian_a.get_log_weight() verifyNoUnwantedInteractions() unstub()
def test_get_log_weight(self): """ Test that the correct log-weight is returned. """ gaussian_a = Gaussian(cov=2.0, mean=1.0, log_weight=0.0, var_names=["a"]) self.assertEqual(gaussian_a.get_log_weight(), 0.0)
def test_copy_2d_canform(self): """ Test that the copy function returns a identical copy of a Gaussian in canonical form. """ gaussian = Gaussian(prec=[[7.0, 2.0], [2.0, 1.0]], h_vec=[4.0, 1.0], g_val=0.0, var_names=["a", "b"]) self.assertTrue(gaussian.equals(gaussian.copy()))
def test_copy_2d_covform(self): """ Test that the copy function returns a identical copy of a two dimensional Gaussian in covariance form. """ gaussian = Gaussian(cov=[[7.0, 2.0], [2.0, 1.0]], mean=[4.0, 1.0], log_weight=0.0, var_names=["a", "b"]) self.assertTrue(gaussian.equals(gaussian.copy()))
def test_impossible_update_canform(self): """ Test that the _update_canform raises a LinAlgError when the covariance matrix is not invertible. """ gaussian = Gaussian(cov=np.zeros([2, 2]), mean=[0.0, 0.0], log_weight=0.0, var_names=["a", "b"]) with self.assertRaises(np.linalg.LinAlgError): gaussian._update_canform()
def test_get_mean(self): """ Test that the correct mean is returned. """ gaussian_a = Gaussian(cov=2.0, mean=1.0, log_weight=0.0, var_names=["a"]) self.assertTrue( np.array_equal(gaussian_a.get_mean(), np.array([[1.0]])))
def test_impossible_update_covform(self): """ Test that the _update_covform raises a LinAlgError when the precision matrix is not invertible. """ gaussian = Gaussian(prec=np.zeros([2, 2]), h_vec=[0.0, 0.0], g_val=0.0, var_names=["a", "b"]) with self.assertRaises(Exception): gaussian._update_covform()
def test_equals_different_factor(self): """ Test that the equals function raises a value error when compared to a different type of factor. """ gaussian_1 = Gaussian(cov=1, mean=1, log_weight=1.0, var_names=["a"]) categorical_factor = Categorical(var_names=["a", "b"], probs_table={(0, 0): 0.1}, cardinalities=[2, 2]) with self.assertRaises(ValueError): gaussian_1.equals(categorical_factor)
def test_copy_no_form(self): """ Test that the copy function raises an exception when a Gaussian does not have either of its form updated. """ gaussian_no_form = Gaussian(cov=1.0, mean=0.0, log_weight=0.0, var_names=["a"]) gaussian_no_form.covform = False with self.assertRaises(Exception): gaussian_no_form.copy()
def test_log_potential_no_form(self): """ Test that the log_potential function raises an exception if the Gaussian has no form. """ gaussian = Gaussian(cov=1.0, mean=1.0, log_weight=np.log(1.0), var_names=["a"]) gaussian.covform = False with self.assertRaises(Exception): gaussian.log_potential(x_val=0)
def test_get_complex_log_weight(self): """ Test that the _get_complex_log_weight returns the correct value. """ gaussian_ab = Gaussian(prec=[[-1.0, 0.0], [0.0, 1.0]], h_vec=[0.0, 0.0], g_val=0.0, var_names=["a", "b"]) actual_complex_weight = gaussian_ab._get_complex_weight() expected_complex_weight = cmath.exp(0.5 * cmath.log( (-1.0) * (2.0 * np.pi)**2)) self.assertAlmostEqual(actual_complex_weight, expected_complex_weight)
def test_get_g_from_covform(): """ Test that the _update_canform function is called before returning the g parameter. """ gaussian_a = Gaussian(cov=2.0, mean=1.0, log_weight=0.0, var_names=["a"]) expect(Gaussian, times=1)._update_canform() gaussian_a.get_g() verifyNoUnwantedInteractions() unstub()
def test_potential(self): """ Check that the potential method returns the correct value. """ cov = np.array([[2.0, 1.0], [1.0, 2.0]]) mean = np.array([[4.0], [5.0]]) gaussian = Gaussian(cov=cov, mean=mean, log_weight=np.log(1.0), var_names=["a", "b"]) actual_max_potential = gaussian.potential(x_val=mean) expected_max_potential = 1.0 / np.sqrt(np.linalg.det(2 * np.pi * cov)) self.assertEqual(actual_max_potential, expected_max_potential)
def test_constructor_insufficient_parameters(self): """ Test that the constructor raises an exception when insufficient parameters are supplied """ # no var_names with self.assertRaises(Exception): Gaussian() # incomplete covariance form parameters with self.assertRaises(ValueError): Gaussian(cov=5, var_names=["a"]) # incomplete canonical form parameters with self.assertRaises(ValueError): Gaussian(prec=5, var_names=["a"])
def test_equals_different_order(self): """ Test that the equals function can identify identical Gaussians with different order variables. """ gaussian_1 = Gaussian(cov=[[1, 0], [0, 2]], mean=[1, 2], log_weight=1.0, var_names=["a", "b"]) gaussian_2 = Gaussian(cov=[[2, 0], [0, 1]], mean=[2, 1], log_weight=1.0, var_names=["b", "a"]) self.assertTrue(gaussian_1.equals(gaussian_2))
def test_invert(self): """ Test that the _invert method returns a Gaussian with the negated parameters. """ gaussian = Gaussian(prec=[[1.0, 2.0], [2.0, 3.0]], h_vec=[4.0, 5.0], g_val=6.0, var_names=["a", "b"]) expected_inv_gaussian = Gaussian(prec=[[-1.0, -2.0], [-2.0, -3.0]], h_vec=[-4.0, -5.0], g_val=-6.0, var_names=["a", "b"]) actual_inv_gaussian = gaussian._invert() self.assertTrue(actual_inv_gaussian.equals(expected_inv_gaussian))
def test_form_conversion(self): """ Test that conversion from one form to the other and back results in the same Gaussian parameters. """ gaussian_ab = Gaussian(cov=[[7.0, 2.0], [2.0, 1.0]], mean=[4.0, 1.0], log_weight=0.0, var_names=["a", "b"]) gaussian_ab_copy = gaussian_ab.copy() gaussian_ab_copy._update_canform() gaussian_ab_copy.covform = False gaussian_ab_copy._update_covform() self.assertTrue(gaussian_ab.equals(gaussian_ab_copy))
def test__repr__(self): """ Test that the __repr__ method returns the correct result. """ gaussian = Gaussian(prec=[[1.0, 0.0], [0.0, 1.0]], h_vec=[0.0, 0.0], g_val=0.0, var_names=["a", "b"]) expected_repr_string = ( "vars = ['a', 'b']\nprec = \n[[1. 0.]\n [0. 1.]]\nh = \n[[0.]\n [0.]]\ng = \n0.0\n" + "is_vacuous: False\ncov = \n[[1. 0.]\n [0. 1.]]" + "\nmean = \n[[0.]\n [0.]]\nlog_weight = \n1.8378770664093453\n" ) actual_repr_string = gaussian.__repr__() self.assertEqual(actual_repr_string, expected_repr_string)
def test_multiply_both_sides(self): """ Test that the multiply function results in the correct joint distribution when a factor with the conditional scope is received first and a conditioning scope factor is received afterwards. """ conditional_update_cov = np.array([[2, 1], [1, 4]]) conditional_update_mean = np.array([[5], [4]]) conditional_update_factor = Gaussian(cov=conditional_update_cov, mean=conditional_update_mean, log_weight=0.0, var_names=["c", "d"]) a_mat = np.array([[2, 0], [0, 1]]) def transform(x_val, _): return a_mat.dot(x_val) noise_cov = np.array([[1, 0], [0, 1]]) nlg_factor = NonLinearGaussian( conditioning_vars=["a", "b"], conditional_vars=["c", "d"], transform=transform, noise_cov=noise_cov, ) conditioning_cov = np.array([[3, 1], [1, 5]]) conditioning_mean = np.array([[2], [3]]) conditioning_gaussian = Gaussian(cov=conditioning_cov, mean=conditioning_mean, log_weight=0.0, var_names=["a", "b"]) # TODO: Consider replacing this with hardcoded specific params. joint_cov, joint_mean = con_params_to_joint(conditioning_cov, conditioning_mean, a_mat, noise_cov) expected_joint = Gaussian(cov=joint_cov, mean=joint_mean, log_weight=0.0, var_names=["a", "b", "c", "d"]) expected_joint = expected_joint.multiply( conditional_update_factor.copy()) nlg_factor = nlg_factor.multiply(conditional_update_factor) nlg_factor = nlg_factor.multiply(conditioning_gaussian) conditional_update_factor.show() self.assertTrue(expected_joint.equals(nlg_factor.joint_distribution))
def marginalize(self, vrs, keep=True): """ Integrate out variables from this factor. :param vrs: (list) a subset of variables in the factor's scope :param keep: Whether to keep or sum out vrs :return: the resulting marginal :rtype: Gaussian """ vrs_to_keep = super().get_marginal_vars(vrs, keep=keep) assert set(vrs_to_keep) <= set(self.var_names), "Error: asked to keep vars not in factor." self_copy = self.copy() self_copy._recompute_joint() if self_copy._joint_distribution._is_vacuous: if set(vrs_to_keep) <= set(self_copy.conditioning_vars): if self_copy.conditioning_factor is not None: if set(vrs_to_keep) <= set(self_copy.conditioning_factor.var_names): marginal = self_copy.conditioning_factor.marginalize(vrs_to_keep, keep=True) return marginal if set(vrs_to_keep) <= set(self_copy.conditional_vars): if self_copy.conditional_update_factor is not None: if set(vrs_to_keep) <= set(self_copy.conditional_update_factor.var_names): marginal = self_copy.conditional_update_factor.marginalize(vrs_to_keep, keep=True) marginal._add_log_weight(self.log_weight) return marginal else: return Gaussian.make_vacuous(vrs_to_keep) return self_copy._joint_distribution.marginalize(vrs_to_keep, keep=True)
def test_is_vacuous_vacuous(self): """ Test that the is_vacuous property function identifies a Gaussian as vacuous if the vacuous member variable is set to True. """ dims = 3 var_names = [str(i) for i in range(dims)] prec = np.eye(dims) * 1.0 h_vec = np.zeros([dims, 1]) g_val = 0.0 gauss = Gaussian(var_names=var_names, prec=prec, h_vec=h_vec, g_val=g_val) gauss._is_vacuous = True self.assertTrue(gauss.is_vacuous)
def test_marginalise_2d_canform(self): """ Test that the Gaussian marginalisation function returns the correct result for a two dimensional Gaussians. """ gaussian = Gaussian(cov=[[7.0, 2.0], [2.0, 1.0]], mean=[4.0, 1.0], log_weight=0.0, var_names=["a", "b"]) expected_result = Gaussian(cov=7.0, mean=4.0, log_weight=0.0, var_names=["a"]) expected_result._update_canform() gaussian._update_canform() actual_result = gaussian.marginalize(vrs=["a"], keep=True) self.assertTrue(expected_result.equals(actual_result))