Repository: pgmpy/pgmpy
Pull request #709 (2016-07-19 07:57:42)

Linear Gaussain CPD

coveralls: 
[![Coverage Status](https://coveralls.io/builds/7109431/badge)](https://coveralls.io/builds/7109431)

Coverage decreased (-0.2%) to 95.986% when pulling **405092be81304a8bd5dc71c4f91c0126b2a00a4f on yashu-seth:linear_gaussian_cpd** into **e85706c96855ac66ecbf55366d34037a1a378076 on pgmpy:dev**.

ankurankan: @yashu-seth There are merge conflicts in this. And please let me know when this is ready for review.

Patch:
diff --git a/pgmpy/factors/__init__.py b/pgmpy/factors/__init__.py
index 9402b76b..8186637a 100644
--- a/pgmpy/factors/__init__.py
+++ b/pgmpy/factors/__init__.py
@@ -6,6 +6,7 @@ from .continuous.ContinuousNode import ContinuousNode
 from .continuous.ContinuousFactor import ContinuousFactor
 from .continuous.JointGaussianDistribution import JointGaussianDistribution
 from .continuous.CanonicalFactor import CanonicalFactor
+from .continuous.LinearGaussianCPD import LinearGaussianCPD
 
 __all__ = ['Factor',
            'State',
@@ -19,4 +20,5 @@ __all__ = ['Factor',
            'ContinuousNode',
            'ContinuousFactor',
            'JointGaussianDistribution',
-           'CanonicalFactor']
+           'CanonicalFactor',
+           'LinearGaussianCPD']
diff --git a/pgmpy/factors/continuous/JointGaussianDistribution.py b/pgmpy/factors/continuous/JointGaussianDistribution.py
index 91b6adba..a0ac74c2 100644
--- a/pgmpy/factors/continuous/JointGaussianDistribution.py
+++ b/pgmpy/factors/continuous/JointGaussianDistribution.py
@@ -99,7 +99,7 @@ class JointGaussianDistribution(ContinuousFactor):
         ----------
 
         variables: iterator
-                List of variables over which to marginalize.
+                List of variables over which marginalization is to be done.
 
         inplace: boolean
                 If inplace=True it will modify the distribution itself,
diff --git a/pgmpy/factors/continuous/LinearGaussianCPD.py b/pgmpy/factors/continuous/LinearGaussianCPD.py
new file mode 100644
index 00000000..83a18528
--- /dev/null
+++ b/pgmpy/factors/continuous/LinearGaussianCPD.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import division
+
+import numpy as np
+from scipy.stats import multivariate_normal
+
+from pgmpy.extern import six
+from pgmpy.factors import ContinuousFactor
+
+
+class LinearGaussianCPD(ContinuousFactor):
+    u"""
+    For, X -> Y the Linear Gaussian model assumes that the mean
+    of Y is a linear function of mean of X and the variance of Y does
+    not depend on X.
+
+    For example,
+    p(Y|X) = N(-2x + 0.9 ; 1)
+    Here, x is the mean of the variable X.
+
+    Let Y be a continuous variable with continuous parents
+    X1 ............ Xk . We say that Y has a linear Gaussian CPD
+    if there are parameters β0,.........βk and σ2 such that,
+
+    p(Y |x1.......xk) = N(β0 + x1*β1 + ......... + xk*βk ; σ2)
+
+    In vector notation,
+
+    p(Y |x) = N(β0 + β.T * x ; σ2)
+
+    """
+    def __init__(self, variable, beta_0, variance, evidence=[], beta_vector=[]):
+        """
+        Parameters
+        ----------
+
+        variable: any hashable python object
+            The variable whose CPD is defined.
+
+        beta_0: int, float
+            Represents the constant term in the linear equation.
+
+        variance: int, float
+            The variance of the variable defined.
+
+        evidence: iterable of any hashabale python objects
+            An iterable of the parents of the variable. None
+            if there are no parents.
+
+        beta_vector: iterable of int or float
+            An iterable representing the coefficient vector of the linear equation.
+
+        Examples
+        --------
+
+        # For P(Y| X1, X2, X3) = N(-2x1 + 3x2 + 7x3 + 0.2; 9.6)
+
+        >>> cpd = LinearGaussianCPD('Y', 0.2, 9.6, ['X1', 'X2', 'X3'], [-2, 3, 7])
+        >>> cpd.variable
+        'Y'
+        >>> cpd.variance
+        9.6
+        >>> cpd.evidence
+        ['x1', 'x2', 'x3']
+        >>> cpd.beta_vector
+        [-2, 3, 7]
+        >>> cpd.beta_0
+        0.2
+
+        """
+        self.variable = variable
+        self.beta_0 = beta_0
+        self.variance = variance
+
+        if len(evidence) != len(beta_vector):
+            raise ValueError("The number of variables in evidence must be equal to the "
+                             "length of the beta vector.")
+
+        self.evidence = evidence
+        self.beta_vector = np.asarray(beta_vector)
+
+        variables = [variable] + evidence
+        super(LinearGaussianCPD, self).__init__(variables, None)
+
+    @property
+    def pdf(self):
+
+        def _pdf(*args):
+            # The first element of args is the value of the variable on which CPD is defined
+            # and the rest of the elements give the mean values of the parent variables.
+            mean = sum([arg * coeff for (arg, coeff) in zip(args[1:], self.beta_vector)]) + self.beta_0
+            return multivariate_normal.pdf(args[0], np.array(mean), np.array([[self.variance]]))
+
+        return _pdf
+
+    def copy(self):
+        """
+        Return a copy of the distribution.
+
+        Returns
+        -------
+        LinearGaussianCPD: copy of the distribution
+
+        Examples
+        --------
+        >>> import numpy as np
+        >>> from pgmpy.factors import LinearGaussianCPD
+        >>> cpd = LinearGaussianCPD('Y', 0.2, 9.6, ['X1', 'X2', 'X3'], [-2, 3, 7])
+        >>> copy_cpd = cpd.copy()
+        >>> copy_cpd.variable
+        'Y'
+        >>> copy_cpd.evidence
+        ['X1', 'X2', 'X3']
+ 
+        """
+        copy_cpd = LinearGaussianCPD(self.variable, self.beta_0, self.variance,
+                                     list(self.evidence), self.beta_vector)
+
+        return copy_cpd
+
+    def __str__(self):
+        if self.evidence and list(self.beta_vector):
+            # P(Y| X1, X2, X3) = N(-2*X1_mu + 3*X2_mu + 7*X3_mu; 0.2)
+            rep_str = "P(" + str(self.variable) + "| " + ", ".join([str(var) for var in self.evidence]) + ") = " +\
+                      "N(" + " + ".join(["{coeff}*{parent}_mu".format(coeff=coeff, parent=parent)
+                                        for coeff, parent in zip(self.beta_vector,
+                                                                 self.evidence)]) + "; " + str(self.beta_0) + ")"
+        else:
+            # P(X) = N(1, 4)
+            rep_str = "P({X}) = N({beta_0}; {variance})".format(X=str(self.variable), beta_0=str(self.beta_0),
+                                                                variance=str(self.variance))
+        return rep_str
diff --git a/pgmpy/models/BayesianModel.py b/pgmpy/models/BayesianModel.py
index 0b849181..50d4a476 100644
--- a/pgmpy/models/BayesianModel.py
+++ b/pgmpy/models/BayesianModel.py
@@ -177,6 +177,10 @@ class BayesianModel(DirectedGraph):
             The node whose CPD we want. If node not specified returns all the
             CPDs added to the model.
 
+        Returns
+        -------
+        A list of TabularCPDs.
+
         Examples
         --------
         >>> from pgmpy.models import BayesianModel
@@ -204,7 +208,7 @@ class BayesianModel(DirectedGraph):
 
         Parameters
         ----------
-        *cpds: TabularCPD, TreeCPD, RuleCPD object
+        *cpds: TabularCPD object
             A CPD object on any subset of the variables of the model which
             is to be associated with the model.
 
diff --git a/pgmpy/models/__init__.py b/pgmpy/models/__init__.py
index f0ab5135..3baefeca 100644
--- a/pgmpy/models/__init__.py
+++ b/pgmpy/models/__init__.py
@@ -7,6 +7,7 @@ from .JunctionTree import JunctionTree
 from .DynamicBayesianNetwork import DynamicBayesianNetwork
 from .MarkovChain import MarkovChain
 from .NaiveBayes import NaiveBayes
+from .continuous.LinearGaussianBayesianNetwork import LinearGaussianBayesianNetwork
 
 __all__ = ['BayesianModel',
            'NoisyOrModel',
@@ -16,4 +17,5 @@ __all__ = ['BayesianModel',
            'ClusterGraph',
            'DynamicBayesianNetwork',
            'MarkovChain',
-           'NaiveBayes']
+           'NaiveBayes',
+           'LinearGaussianBayesianNetwork']
diff --git a/pgmpy/models/continuous/LinearGaussianBayesianNetwork.py b/pgmpy/models/continuous/LinearGaussianBayesianNetwork.py
new file mode 100644
index 00000000..55888a45
--- /dev/null
+++ b/pgmpy/models/continuous/LinearGaussianBayesianNetwork.py
@@ -0,0 +1,240 @@
+from __future__ import division
+
+import numpy as np
+import networkx as nx
+
+from pgmpy.models import BayesianModel
+from pgmpy.factors import LinearGaussianCPD
+from pgmpy.factors import JointGaussianDistribution
+
+
+class LinearGaussianBayesianNetwork(BayesianModel):
+    """
+    A Linear Gaussain Bayesian Network is a Bayesian Network, all
+    of whose variables are continuous, and where all of the CPDs
+    are linear Gaussians.
+
+    An important result is that the linear Gaussian Bayesian Networks
+    are an alternative representation for the class of multivariate
+    Gaussian distributions.
+
+    """
+
+    def add_cpds(self, *cpds):
+        """
+        Add linear Gaussian CPD (Conditional Probability Distribution)
+        to the Bayesian Model.
+
+        Parameters
+        ----------
+        cpds  :  instances of LinearGaussianCPD
+            List of LinearGaussianCPDs which will be associated with the model
+
+        Examples
+        --------
+        >>> from pgmpy.models import LinearGaussianBayesianNetwork
+        >>> from pgmpy.factors import LinearGaussianCPD
+        >>> model = LinearGaussianBayesianNetwork([('x1', 'x2'), ('x2', 'x3')])
+        >>> cpd1 = LinearGaussianCPD('x1', 1, 4)
+        >>> cpd2 = LinearGaussianCPD('x2', -5, 4, ['x1'], [0.5])
+        >>> cpd3 = LinearGaussianCPD('x3', 4, 3, ['x2'], [-1])
+        >>> model.add_cpds(cpd1, cpd2, cpd3)
+        >>> for cpd in model.cpds:
+                print(cpd)
+
+        P(x1) = N(1; 4)
+        P(x2| x1) = N(0.5*x1_mu); -5)
+        P(x3| x2) = N(-1*x2_mu); 4)
+
+        """
+        for cpd in cpds:
+            if not isinstance(cpd, LinearGaussianCPD):
+                raise ValueError('Only LinearGaussianCPD can be added.')
+
+            if set(cpd.variables) - set(cpd.variables).intersection(
+                    set(self.nodes())):
+                raise ValueError('CPD defined on variable not in the model', cpd)
+
+            for prev_cpd_index in range(len(self.cpds)):
+                if self.cpds[prev_cpd_index].variable == cpd.variable:
+                    logging.warning("Replacing existing CPD for {var}".format(var=cpd.variable))
+                    self.cpds[prev_cpd_index] = cpd
+                    break
+            else:
+                self.cpds.append(cpd)
+
+    def get_cpds(self, node=None):
+        """
+        Returns the cpd of the node. If node is not specified returns all the CPDs
+        that have been added till now to the graph
+
+        Parameter
+        ---------
+        node: any hashable python object (optional)
+            The node whose CPD we want. If node not specified returns all the
+            CPDs added to the model.
+
+        Returns
+        -------
+        A list of linear Gaussian CPDs.
+
+        Examples
+        --------
+        >>> from pgmpy.models import LinearGaussianBayesianNetwork
+        >>> form pgmpy.factors import LinearGaussianCPD
+        >>> model = LinearGaussianBayesianNetwork([('x1', 'x2'), ('x2', 'x3')])
+        >>> cpd1 = LinearGaussianCPD('x1', 1, 4)
+        >>> cpd2 = LinearGaussianCPD('x2', -5, 4, ['x1'], [0.5])
+        >>> cpd3 = LinearGaussianCPD('x3', 4, 3, ['x2'], [-1])
+        >>> model.add_cpds(cpd1, cpd2, cpd3)
+        >>> model.get_cpds()
+        """
+        return super(LinearGaussianBayesianNetwork, self).get_cpds(node)
+
+    def remove_cpds(self, *cpds):
+        """
+        Removes the cpds that are provided in the argument.
+
+        Parameters
+        ----------
+        *cpds: LinearGaussianCPD object
+            A LinearGaussianCPD object on any subset of the variables
+            of the model which is to be associated with the model.
+
+        Examples
+        --------
+        >>> from pgmpy.models import LinearGaussianBayesianNetwork
+        >>> form pgmpy.factors import LinearGaussianCPD
+        >>> model = LinearGaussianBayesianNetwork([('x1', 'x2'), ('x2', 'x3')])
+        >>> cpd1 = LinearGaussianCPD('x1', 1, 4)
+        >>> cpd2 = LinearGaussianCPD('x2', -5, 4, ['x1'], [0.5])
+        >>> cpd3 = LinearGaussianCPD('x3', 4, 3, ['x2'], [-1])
+        >>> model.add_cpds(cpd1, cpd2, cpd3)
+        >>> for cpd in model.get_cpds():
+                print(cpd)
+
+        P(x1) = N(1; 4)
+        P(x2| x1) = N(0.5*x1_mu); -5)
+        P(x3| x2) = N(-1*x2_mu); 4)
+
+        >>> model.remove_cpds(cpd2, cpd3)
+        >>> for cpd in model.get_cpds():
+                print(cpd)
+
+        P(x1) = N(1; 4)
+
+        """
+        return super(LinearGaussianBayesianNetwork, self).remove_cpds(*cpds)
+
+    def to_joint_gaussian(self):
+        """
+        The linear Gaussian Bayesian Networks are an alternative
+        representation for the class of multivariate Gaussian distributions.
+        This method returns an equivalent joint Gaussian distribution.
+
+        Returns
+        -------
+        JointGaussianDistribution: An equivalent joint Gaussian
+                                   distribution for the network.
+
+        Reference
+        ---------
+        Section 7.2, Example 7.3,
+        Probabilistic Graphical Models, Principles and Techniques
+
+        Examples
+        --------
+        >>> from pgmpy.models import LinearGaussianBayesianNetwork
+        >>> form pgmpy.factors import LinearGaussianCPD
+        >>> model = LinearGaussianBayesianNetwork([('x1', 'x2'), ('x2', 'x3')])
+        >>> cpd1 = LinearGaussianCPD('x1', 1, 4)
+        >>> cpd2 = LinearGaussianCPD('x2', -5, 4, ['x1'], [0.5])
+        >>> cpd3 = LinearGaussianCPD('x3', 4, 3, ['x2'], [-1])
+        >>> model.add_cpds(cpd1, cpd2, cpd3)
+        >>> jgd = model.to_joint_gaussian()
+        >>> jgd.variables
+        ['x1', 'x2', 'x3']
+        >>> jgd.mean
+        array([[ 1. ],
+               [-4.5],
+               [ 8.5]])
+        >>> jgd.covariance
+        array([[ 4.,  2., -2.],
+               [ 2.,  5., -5.],
+               [-2., -5.,  8.]])
+
+        """
+        variables = nx.topological_sort(self)
+        mean = np.zeros(len(variables))
+        covariance = np.zeros((len(variables), len(variables)))
+
+        for node_idx in range(len(variables)):
+            cpd = self.get_cpds(variables[node_idx])
+            mean[node_idx] = sum([coeff * mean[variables.index(parent)] for coeff, parent in zip(cpd.beta_vector,
+                                                                                                 cpd.evidence)]) + cpd.beta_0
+            covariance[node_idx, node_idx] = sum([coeff * coeff * covariance[variables.index(parent), variables.index(parent)]
+                                                  for coeff, parent in zip(cpd.beta_vector,
+                                                                           cpd.evidence)]) + cpd.variance
+
+        for node_i_idx in range(len(variables)):
+            for node_j_idx in range(len(variables)):
+                if covariance[node_j_idx, node_i_idx] != 0:
+                    covariance[node_i_idx, node_j_idx] = covariance[node_j_idx, node_i_idx]
+                else:
+                    cpd_j = self.get_cpds(variables[node_j_idx])
+                    covariance[node_i_idx, node_j_idx] = sum([coeff * covariance[node_i_idx, variables.index(parent)]
+                                                             for coeff, parent in zip(cpd_j.beta_vector, cpd_j.evidence)])
+
+        return JointGaussianDistribution(variables, mean, covariance)
+
+    def check_model(self):
+        """
+        Checks the model for various errors. This method checks for the following
+        error -
+
+        * Checks if the CPDs associated with nodes are consistent with their parents.
+
+        Returns
+        -------
+        check: boolean
+            True if all the checks pass.
+
+        """
+        for node in self.nodes():
+            cpd = self.get_cpds(node=node)
+
+            if isinstance(cpd, LinearGaussianCPD):
+                if set(cpd.evidence) != set(self.get_parents(node)):
+                    raise ValueError("CPD associated with %s doesn't have "
+                                     "proper parents associated with it." % node)
+        return True
+
+    def get_cardinality(self, node):
+        """
+        Cardinality is not defined for continuous variables.
+        """
+        raise ValueError("Cardinality is not defined for continuous variables.")
+
+    def fit(self, data, estimator_type=None, state_names=[], complete_samples_only=True, **kwargs):
+        """
+        For now, fit method has not been implemented for LinearGaussianBayesianNetwork.
+        """
+        raise NotImplementedError("fit method has not been implemented for LinearGaussianBayesianNetwork.")
+
+    def predict(self, data):
+        """
+        For now, predict method has not been implemented for LinearGaussianBayesianNetwork.
+        """
+        raise NotImplementedError("predict method has not been implemented for LinearGaussianBayesianNetwork.")
+
+    def to_markov_model(self):
+        """
+        For now, to_markov_model method has not been implemented for LinearGaussianBayesianNetwork.
+        """
+        raise NotImplementedError("to_markov_model method has not been implemented for LinearGaussianBayesianNetwork.")
+
+    def is_imap(self, JPD):
+        """
+        For now, is_imap method has not been implemented for LinearGaussianBayesianNetwork.
+        """
+        raise NotImplementedError("is_imap method has not been implemented for LinearGaussianBayesianNetwork.")


Test patch:
diff --git a/pgmpy/models/continuous/__init__.py b/pgmpy/models/continuous/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/pgmpy/tests/test_factors/test_continuous/test_Linear_Gaussain_CPD.py b/pgmpy/tests/test_factors/test_continuous/test_Linear_Gaussain_CPD.py
new file mode 100644
index 00000000..93603b4d
--- /dev/null
+++ b/pgmpy/tests/test_factors/test_continuous/test_Linear_Gaussain_CPD.py
@@ -0,0 +1,75 @@
+import unittest
+
+import numpy as np
+import numpy.testing as np_test
+
+from pgmpy.factors import LinearGaussianCPD
+
+class TestLGCPD(unittest.TestCase):
+    def test_class_init(self):
+        cpd1 = LinearGaussianCPD('x', 0.23, 0.56)
+        self.assertEqual(cpd1.variable, 'x')
+        self.assertEqual(cpd1.beta_0, 0.23)
+        self.assertEqual(cpd1.variance, 0.56)
+
+        cpd2 = LinearGaussianCPD('y', 0.67, 2, ['x1', 'x2', 'x3'],
+                                 [1, 4.56, 8])
+        self.assertEqual(cpd2.variable, 'y')
+        self.assertEqual(cpd2.beta_0, 0.67)
+        self.assertEqual(cpd2.variance, 2)
+        self.assertEqual(cpd2.evidence, ['x1', 'x2', 'x3'])
+        np_test.assert_array_equal(cpd2.beta_vector, [1, 4.56, 8])
+
+        self.assertRaises(ValueError, LinearGaussianCPD, 'x', 1, 2,
+                          ['a', 'b', 'c'], [1, 2])
+        self.assertRaises(ValueError, LinearGaussianCPD, 'x', 1, 2,
+                          ['a', 'b'], [1, 2, 3])
+
+    def test_pdf(self):
+        cpd1 = LinearGaussianCPD('x', 0.23, 0.56)
+        cpd2 = LinearGaussianCPD('y', 0.67, 2, ['x1', 'x2', 'x3'],
+                                 [1, 4.56, 8])
+        np_test.assert_almost_equal(cpd1.assignment(1), 0.3139868)
+        np_test.assert_almost_equal(cpd2.assignment(1, 1.2, 2.3, 3.4), 1.076e-162)
+
+    def test_copy(self):
+        cpd = LinearGaussianCPD('y', 0.67, 2, ['x1', 'x2', 'x3'],
+                                 [1, 4.56, 8])
+        copy = cpd.copy()
+
+        self.assertEqual(cpd.variable, copy.variable)
+        self.assertEqual(cpd.beta_0, copy.beta_0)
+        self.assertEqual(cpd.variance, copy.variance)
+        np_test.assert_array_equal(cpd.beta_vector, copy.beta_vector)
+        self.assertEqual(cpd.evidence, copy.evidence)
+
+        cpd.variable = 'z'
+        self.assertEqual(copy.variable, 'y')
+        cpd.variance = 0
+        self.assertEqual(copy.variance, 2)
+        cpd.beta_0 = 1
+        self.assertEqual(copy.beta_0, 0.67)
+        cpd.evidence = ['p', 'q', 'r']
+        self.assertEqual(copy.evidence, ['x1', 'x2', 'x3'])
+        cpd.beta_vector = [2, 2, 2]
+        np_test.assert_array_equal(copy.beta_vector, [1, 4.56, 8])
+
+        copy = cpd.copy()
+
+        copy.variable = 'k'
+        self.assertEqual(cpd.variable, 'z')
+        copy.variance = 0.3
+        self.assertEqual(cpd.variance, 0)
+        copy.beta_0 = 1.5
+        self.assertEqual(cpd.beta_0, 1)
+        copy.evidence = ['a', 'b', 'c']
+        self.assertEqual(cpd.evidence, ['p', 'q', 'r'])
+        copy.beta_vector = [2.2, 2.2, 2.2]
+        np_test.assert_array_equal(cpd.beta_vector, [2, 2, 2])
+
+    def test_str(self):
+        cpd1 = LinearGaussianCPD('x', 0.23, 0.56)
+        cpd2 = LinearGaussianCPD('y', 0.67, 2, ['x1', 'x2', 'x3'],
+                                 [1, 4.56, 8])
+        self.assertEqual(cpd1.__str__(), "P(x) = N(0.23; 0.56)")
+        self.assertEqual(cpd2.__str__(), "P(y| x1, x2, x3) = N(1.0*x1_mu + 4.56*x2_mu + 8.0*x3_mu; 0.67)")
diff --git a/pgmpy/tests/test_models/test_continuous/test_LinearGaussianBayesianNetwork.py b/pgmpy/tests/test_models/test_continuous/test_LinearGaussianBayesianNetwork.py
new file mode 100644
index 00000000..a5ad9e21
--- /dev/null
+++ b/pgmpy/tests/test_models/test_continuous/test_LinearGaussianBayesianNetwork.py
@@ -0,0 +1,75 @@
+import unittest
+
+import numpy as np
+import numpy.testing as np_test
+
+from pgmpy.factors import LinearGaussianCPD
+from pgmpy.factors import TabularCPD
+from pgmpy.models import LinearGaussianBayesianNetwork
+
+
+class TestLGBNMethods(unittest.TestCase):
+    def setUp(self):
+        self.model = LinearGaussianBayesianNetwork([('x1', 'x2'), ('x2', 'x3')])
+        self.cpd1 = LinearGaussianCPD('x1', 1, 4)
+        self.cpd2 = LinearGaussianCPD('x2', -5, 4, ['x1'], [0.5])
+        self.cpd3 = LinearGaussianCPD('x3', 4, 3, ['x2'], [-1])
+
+    def test_add_cpds(self):
+        self.model.add_cpds(self.cpd1)
+        cpd = self.model.get_cpds('x1')
+        self.assertEqual(cpd.variable, self.cpd1.variable)
+        self.assertEqual(cpd.variance, self.cpd1.variance)
+        self.assertEqual(cpd.beta_0, self.cpd1.beta_0)
+
+        self.model.add_cpds(self.cpd2)
+        cpd = self.model.get_cpds('x2')
+        self.assertEqual(cpd.variable, self.cpd2.variable)
+        self.assertEqual(cpd.variance, self.cpd2.variance)
+        self.assertEqual(cpd.beta_0, self.cpd2.beta_0)
+        self.assertEqual(cpd.evidence, self.cpd2.evidence)
+        np_test.assert_array_equal(cpd.beta_vector, self.cpd2.beta_vector)
+
+        self.model.add_cpds(self.cpd3)
+        cpd = self.model.get_cpds('x3')
+        self.assertEqual(cpd.variable, self.cpd3.variable)
+        self.assertEqual(cpd.variance, self.cpd3.variance)
+        self.assertEqual(cpd.beta_0, self.cpd3.beta_0)
+        self.assertEqual(cpd.evidence, self.cpd3.evidence)
+        np_test.assert_array_equal(cpd.beta_vector, self.cpd3.beta_vector)
+
+        tab_cpd = TabularCPD('grade', 3, [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
+                                          [0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
+                                          [0.8, 0.8, 0.8, 0.8, 0.8, 0.8]],
+                                         evidence=['diff', 'intel'], evidence_card=[2, 3])
+        self.assertRaises(ValueError, self.model.add_cpds, tab_cpd)
+        self.assertRaises(ValueError, self.model.add_cpds, 1)
+        self.assertRaises(ValueError, self.model.add_cpds, 1, tab_cpd)
+
+    def test_to_joint_gaussian(self):
+        self.model.add_cpds(self.cpd1, self.cpd2, self.cpd3)
+        jgd = self.model.to_joint_gaussian()
+        self.assertEqual(jgd.variables, ['x1', 'x2', 'x3'])
+        np_test.assert_array_equal(jgd.mean, np.array([[ 1. ],
+                                                       [-4.5],
+                                                       [ 8.5]]))
+        np_test.assert_array_equal(jgd.covariance, np.array([[ 4.,  2., -2.],
+                                                             [ 2.,  5., -5.],
+                                                             [-2., -5.,  8.]]))
+
+    def test_check_model(self):
+        self.model.add_cpds(self.cpd1, self.cpd2, self.cpd3)
+        self.assertEqual(self.model.check_model(), True)
+
+        self.model.add_edge('x1', 'x4')
+        cpd4 = LinearGaussianCPD('x4', 4, 3, ['x2'], [-1])
+        self.model.add_cpds(cpd4)
+
+        self.assertRaises(ValueError, self.model.check_model)
+
+    def test_not_implemented_methods(self):
+        self.assertRaises(ValueError, self.model.get_cardinality, 'x1')
+        self.assertRaises(NotImplementedError, self.model.fit, [[1, 2, 3], [1, 5, 6]])
+        self.assertRaises(NotImplementedError, self.model.predict, [[1, 2, 3], [1, 5, 6]])
+        self.assertRaises(NotImplementedError, self.model.to_markov_model)
+        self.assertRaises(NotImplementedError, self.model.is_imap, [[1, 2, 3], [1, 5, 6]])