Esempio n. 1
0
    def __init__(self, variables, pdf, *args, **kwargs):
        """
        Parameters
        ----------
        variables: list or array-like
            The variables for wich the distribution is defined.

        pdf: function
            The probability density function of the distribution.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable dirichlet distribution with alpha = (1,2)
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.scope()
        ['x', 'y']
        >>> dirichlet_factor.assignment(5,6)
        226800.0
        """
        if not isinstance(variables, (list, tuple, np.ndarray)):
            raise TypeError(
                "variables: Expected type list or array-like, "
                "got type {var_type}".format(var_type=type(variables)))

        if len(set(variables)) != len(variables):
            raise ValueError("Variable names cannot be same.")

        variables = list(variables)

        if isinstance(pdf, str):
            if pdf == "gaussian":
                self.distribution = GaussianDistribution(
                    variables=variables,
                    mean=kwargs["mean"],
                    covariance=kwargs["covariance"],
                )
            else:
                raise NotImplementedError(
                    "{dist} distribution not supported.",
                    "Please use CustomDistribution".format(dist=pdf),
                )

        elif isinstance(pdf, CustomDistribution):
            self.distribution = pdf

        elif callable(pdf):
            self.distribution = CustomDistribution(variables=variables,
                                                   distribution=pdf)

        else:
            raise ValueError(
                "pdf: Expected type: str or function, ",
                "Got: {instance}".format(instance=type(variables)),
            )
    def __init__(self, variables, pdf, *args, **kwargs):
        """
        Parameters
        ----------
        variables: list or array-like
            The variables for wich the distribution is defined.

        pdf: function
            The probability density function of the distribution.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable drichlet ditribution with alpha = (1,2)
        >>> def drichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], drichlet_pdf)
        >>> dirichlet_factor.scope()
        ['x', 'y']
        >>> dirichlet_factor.assignment(5,6)
        226800.0
        """
        if not isinstance(variables, (list, tuple, np.ndarray)):
            raise TypeError("variables: Expected type list or array-like, "
                            "got type {var_type}".format(var_type=type(variables)))

        if len(set(variables)) != len(variables):
            raise ValueError("Variable names cannot be same.")

        variables = list(variables)

        if isinstance(pdf, str):
            if pdf == 'gaussian':
                self.distribution = GaussianDistribution(
                    variables=variables,
                    mean=kwargs['mean'],
                    covariance=kwargs['covariance'])
            else:
                raise NotImplementedError("{dist} distribution not supported.",
                                          "Please use CustomDistribution".
                                          format(dist=pdf))

        elif isinstance(pdf, CustomDistribution):
            self.distribution = pdf

        elif callable(pdf):
            self.distribution = CustomDistribution(
                variables=variables,
                distribution=pdf)

        else:
            raise ValueError("pdf: Expected type: str or function, ",
                             "Got: {instance}".format(instance=type(variables)))
    def test_class_init(self):
        phi1 = CustomDistribution(["x", "y"], self.pdf1)
        self.assertEqual(phi1.variables, ["x", "y"])
        self.assertEqual(phi1._pdf, self.pdf1)

        phi2 = CustomDistribution(["x1", "x2"], self.pdf2)
        self.assertEqual(phi2.variables, ["x1", "x2"])
        self.assertEqual(phi2._pdf, self.pdf2)

        phi3 = CustomDistribution(["x", "y", "z"], self.pdf3)
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        self.assertEqual(phi3._pdf, self.pdf3)
Esempio n. 4
0
    def test_class_init(self):
        phi1 = CustomDistribution(['x', 'y'], self.pdf1)
        self.assertEqual(phi1.variables, ['x', 'y'])
        self.assertEqual(phi1._pdf, self.pdf1)

        phi2 = CustomDistribution(['x1', 'x2'], self.pdf2)
        self.assertEqual(phi2.variables, ['x1', 'x2'])
        self.assertEqual(phi2._pdf, self.pdf2)

        phi3 = CustomDistribution(['x', 'y', 'z'], self.pdf3)
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        self.assertEqual(phi3._pdf, self.pdf3)
    def test_normalize(self):
        def pdf2(x1, x2):
            return 2 * self.pdf2(x1, x2)

        phi2 = CustomDistribution(["x1", "x2"], pdf2)
        phi4 = phi2.copy()

        phi4.normalize()
        self.assertEqual(phi4.variables, phi2.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

        phi4 = phi2.normalize(inplace=False)
        self.assertEqual(phi4.variables, phi4.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))
    def test_normalize(self):
        def pdf2(x1, x2):
            return 2 * self.pdf2(x1, x2)

        phi2 = CustomDistribution(['x1', 'x2'], pdf2)
        phi4 = phi2.copy()

        phi4.normalize()
        self.assertEqual(phi4.variables, phi2.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

        phi4 = phi2.normalize(inplace=False)
        self.assertEqual(phi4.variables, phi4.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))
 def setUp(self):
     self.phi1 = CustomDistribution(["x", "y"], self.pdf1)
     self.phi2 = CustomDistribution(["x1", "x2"], self.pdf2)
     self.phi3 = CustomDistribution(["x", "y", "z"], self.pdf3)
     self.phi4 = CustomDistribution(["x1", "x2", "x3"], self.pdf4)
class TestCustomDistributionMethods(unittest.TestCase):
    def pdf1(self, x, y):
        return np.power(x, 1) * np.power(y, 2) / beta(x, y)

    def pdf2(self, x1, x2):
        return multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])

    def pdf3(self, x, y, z):
        return z * (np.power(x, 1) * np.power(y, 2)) / beta(x, y)

    def pdf4(self, x1, x2, x3):
        return multivariate_normal.pdf([x1, x2, x3], [0, 0, 0],
                                       [[1, 0, 0], [0, 1, 0], [0, 0, 1]])

    def setUp(self):
        self.phi1 = CustomDistribution(["x", "y"], self.pdf1)
        self.phi2 = CustomDistribution(["x1", "x2"], self.pdf2)
        self.phi3 = CustomDistribution(["x", "y", "z"], self.pdf3)
        self.phi4 = CustomDistribution(["x1", "x2", "x3"], self.pdf4)

    def test_variables(self):
        self.assertEqual(self.phi1.variables, self.phi1._variables)
        self.assertEqual(self.phi2.variables, self.phi2._variables)
        self.assertEqual(self.phi3.variables, self.phi3._variables)

    def test_assignment(self):
        self.assertEqual(self.phi1.assignment(1.212, 2), self.pdf1(1.212, 2))
        self.assertEqual(self.phi2.assignment(1, -2.231), self.pdf2(1, -2.231))
        self.assertEqual(self.phi3.assignment(1.212, 2.213, -3),
                         self.pdf3(1.212, 2.213, -3))

    def test_reduce(self):
        phi1 = self.phi1.copy()
        phi1.reduce([("x", 1)])

        def reduced_pdf1(y):
            return (np.power(1, 1) * np.power(y, 2)) / beta(1, y)

        self.assertEqual(phi1.variables, ["y"])
        for inp in np.random.rand(4):
            self.assertEqual(phi1._pdf(inp), reduced_pdf1(inp))
            self.assertEqual(phi1._pdf(y=inp), reduced_pdf1(inp))

        phi1 = self.phi1.reduce([("x", 1)], inplace=False)
        self.assertEqual(phi1.variables, ["y"])
        for inp in np.random.rand(4):
            self.assertEqual(phi1._pdf(inp), reduced_pdf1(inp))
            self.assertEqual(phi1._pdf(y=inp), reduced_pdf1(inp))

        phi2 = self.phi2.copy()
        phi2.reduce([("x2", 7.213)])

        def reduced_pdf2(x1):
            return multivariate_normal.pdf([x1, 7.213], [0, 0],
                                           [[1, 0], [0, 1]])

        self.assertEqual(phi2.variables, ["x1"])
        for inp in np.random.rand(4):
            self.assertEqual(phi2._pdf(inp), reduced_pdf2(inp))
            self.assertEqual(phi2._pdf(x1=inp), reduced_pdf2(inp))

        phi2 = self.phi2.reduce([("x2", 7.213)], inplace=False)
        self.assertEqual(phi2.variables, ["x1"])
        for inp in np.random.rand(4):
            self.assertEqual(phi2._pdf(inp), reduced_pdf2(inp))
            self.assertEqual(phi2._pdf(x1=inp), reduced_pdf2(inp))

        phi3 = self.phi3.copy()
        phi3.reduce([("y", 0.112), ("z", 23)])

        def reduced_pdf4(x):
            return 23 * (np.power(x, 1) * np.power(0.112, 2)) / beta(x, 0.112)

        self.assertEqual(phi3.variables, ["x"])
        for inp in np.random.rand(4):
            self.assertEqual(phi3._pdf(inp), reduced_pdf4(inp))
            self.assertEqual(phi3._pdf(x=inp), reduced_pdf4(inp))

        phi3 = self.phi3.copy()
        phi3.reduce([("y", 0.112)])

        def reduced_pdf3(x, z):
            return z * (np.power(x, 1) * np.power(0.112, 2)) / beta(x, 0.112)

        self.assertEqual(phi3.variables, ["x", "z"])
        for inp in np.random.rand(4, 2):
            self.assertEqual(phi3._pdf(inp[0], inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(x=inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))

        phi3 = self.phi3.reduce([("y", 0.112)], inplace=False)
        self.assertEqual(phi3.variables, ["x", "z"])
        for inp in np.random.rand(4, 2):
            self.assertEqual(phi3._pdf(inp[0], inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(x=inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))

        phi3 = self.phi3.reduce([("y", 0.112), ("z", 23)], inplace=False)
        self.assertEqual(phi3.variables, ["x"])
        for inp in np.random.rand(4):
            self.assertEqual(phi3._pdf(inp), reduced_pdf4(inp))
            self.assertEqual(phi3._pdf(x=inp), reduced_pdf4(inp))

    def test_reduce_error(self):
        self.assertRaises(TypeError, self.phi1.reduce, "x1")
        self.assertRaises(TypeError, self.phi1.reduce, set(["x", "y"]))
        self.assertRaises(TypeError, self.phi1.reduce, {"x": 1, "y": 1})

        self.assertRaises(TypeError, self.phi4.reduce, "x4")
        self.assertRaises(TypeError, self.phi4.reduce, set(["x1", "x2", "x3"]))
        self.assertRaises(TypeError, self.phi4.reduce, {
            "x1": 1,
            "x2": 1,
            "x3": 1
        })

        self.assertRaises(ValueError, self.phi1.reduce, [("z", 3)])
        self.assertRaises(ValueError, self.phi1.reduce, [("x", 0), ("y", 1),
                                                         ("z", 4)])

        self.assertRaises(ValueError, self.phi4.reduce, [("x4", 7)])
        self.assertRaises(ValueError, self.phi4.reduce, [("x1", 1), ("x2", 2),
                                                         ("x3", 3), ("x4", 4)])

    def test_marginalize(self):
        phi2 = self.phi2.copy()
        phi2.marginalize(["x2"])
        self.assertEqual(phi2.variables, ["x1"])
        for inp in np.random.rand(4):
            np_test.assert_almost_equal(
                phi2._pdf(inp), multivariate_normal.pdf([inp], [0], [[1]]))

        phi2 = self.phi2.marginalize(["x2"], inplace=False)
        self.assertEqual(phi2.variables, ["x1"])
        for inp in np.random.rand(4):
            np_test.assert_almost_equal(
                phi2._pdf(inp), multivariate_normal.pdf([inp], [0], [[1]]))

        phi4 = self.phi4.copy()
        phi4.marginalize(["x2"])

        self.assertEqual(phi4.variables, ["x1", "x3"])
        for inp in np.random.rand(4, 2):
            np_test.assert_almost_equal(
                phi4._pdf(inp[0], inp[1]),
                multivariate_normal.pdf([inp[0], inp[1]], [0, 0],
                                        [[1, 0], [0, 1]]),
            )

        phi4.marginalize(["x3"])
        self.assertEqual(phi4.variables, ["x1"])
        for inp in np.random.rand(1):
            np_test.assert_almost_equal(
                phi4._pdf(inp), multivariate_normal.pdf([inp], [0], [[1]]))

        phi4 = self.phi4.marginalize(["x2"], inplace=False)
        self.assertEqual(phi4.variables, ["x1", "x3"])
        for inp in np.random.rand(4, 2):
            np_test.assert_almost_equal(
                phi4._pdf(inp[0], inp[1]),
                multivariate_normal.pdf([inp[0], inp[1]], [0, 0],
                                        [[1, 0], [0, 1]]),
            )

        phi4 = phi4.marginalize(["x3"], inplace=False)
        self.assertEqual(phi4.variables, ["x1"])
        for inp in np.random.rand(1):
            np_test.assert_almost_equal(
                phi4._pdf(inp), multivariate_normal.pdf([inp], [0], [[1]]))

    def test_marginalize_error(self):
        self.assertRaises(TypeError, self.phi1.marginalize, "x1")
        self.assertRaises(TypeError, self.phi1.marginalize, set(["x", "y"]))
        self.assertRaises(TypeError, self.phi1.marginalize, {"x": 1, "y": 1})

        self.assertRaises(TypeError, self.phi4.marginalize, "x4")
        self.assertRaises(TypeError, self.phi4.marginalize,
                          set(["x1", "x2", "x3"]))
        self.assertRaises(TypeError, self.phi4.marginalize, {
            "x1": 1,
            "x2": 1,
            "x3": 1
        })

        self.assertRaises(ValueError, self.phi1.marginalize, ["z"])
        self.assertRaises(ValueError, self.phi1.marginalize, ["x", "y", "z"])

        self.assertRaises(ValueError, self.phi4.marginalize, ["x4"])
        self.assertRaises(ValueError, self.phi4.marginalize,
                          ["x1", "x2", "x3", "x4"])

    def test_normalize(self):
        def pdf2(x1, x2):
            return 2 * self.pdf2(x1, x2)

        phi2 = CustomDistribution(["x1", "x2"], pdf2)
        phi4 = phi2.copy()

        phi4.normalize()
        self.assertEqual(phi4.variables, phi2.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

        phi4 = phi2.normalize(inplace=False)
        self.assertEqual(phi4.variables, phi4.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

    def test_operate(self):
        phi1 = self.phi1.copy()
        phi1._operate(self.phi2, "product")
        self.assertEqual(phi1.variables, ["x", "y", "x1", "x2"])
        for inp in np.random.rand(4, 4):
            self.assertEqual(
                phi1._pdf(*inp),
                self.phi1._pdf(inp[0], inp[1]) *
                self.phi2._pdf(inp[2], inp[3]),
            )

        phi1 = self.phi1._operate(self.phi2, "product", inplace=False)
        self.assertEqual(phi1.variables, ["x", "y", "x1", "x2"])
        for inp in np.random.rand(4, 4):
            self.assertEqual(
                phi1._pdf(*inp),
                self.phi1._pdf(inp[0], inp[1]) *
                self.phi2._pdf(inp[2], inp[3]),
            )

        phi1 = self.phi1 * self.phi2
        self.assertEqual(phi1.variables, ["x", "y", "x1", "x2"])
        for inp in np.random.rand(4, 4):
            self.assertEqual(
                phi1._pdf(*inp),
                self.phi1._pdf(inp[0], inp[1]) *
                self.phi2._pdf(inp[2], inp[3]),
            )

        phi3 = self.phi3.copy()
        phi3._operate(self.phi1, "product")
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) * self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3._operate(self.phi1, "product", inplace=False)
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) * self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3 * self.phi1
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) * self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3.copy()
        phi3._operate(self.phi1, "divide")
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) / self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3._operate(self.phi1, "divide", inplace=False)
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) / self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3 / self.phi1
        self.assertEqual(phi3.variables, ["x", "y", "z"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi3._pdf(*inp),
                self.phi3._pdf(*inp) / self.phi1._pdf(inp[0], inp[1]))

        phi4 = self.phi4.copy()
        phi4._operate(self.phi2, "product")
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) * self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4._operate(self.phi2, "product", inplace=False)
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) * self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4 * self.phi2
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) * self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4.copy()
        phi4._operate(self.phi2, "divide")
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) / self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4._operate(self.phi2, "divide", inplace=False)
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) / self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4 / self.phi2
        self.assertEqual(phi4.variables, ["x1", "x2", "x3"])
        for inp in np.random.rand(4, 3):
            self.assertEqual(
                phi4._pdf(*inp),
                self.phi4._pdf(*inp) / self.phi2._pdf(inp[0], inp[1]))

    def test_operate_error(self):
        self.assertRaises(TypeError, self.phi1._operate, 1, "product")
        self.assertRaises(TypeError, self.phi1._operate, 1, "divide")
        self.assertRaises(TypeError, self.phi1._operate, "1", "product")
        self.assertRaises(TypeError, self.phi1._operate, "1", "divide")
        self.assertRaises(TypeError, self.phi1._operate, self.phi2._pdf,
                          "product")
        self.assertRaises(TypeError, self.phi1._operate, self.phi2._pdf,
                          "divide")
        self.assertRaises(TypeError, self.phi1._operate, [1], "product")
        self.assertRaises(TypeError, self.phi1._operate, [1], "divide")

        self.assertRaises(TypeError, self.phi4._operate, 1, "product")
        self.assertRaises(TypeError, self.phi4._operate, 1, "divide")
        self.assertRaises(TypeError, self.phi4._operate, "1", "product")
        self.assertRaises(TypeError, self.phi4._operate, "1", "divide")
        self.assertRaises(TypeError, self.phi4._operate, self.phi2._pdf,
                          "product")
        self.assertRaises(TypeError, self.phi4._operate, self.phi2._pdf,
                          "divide")
        self.assertRaises(TypeError, self.phi4._operate, [1], "product")
        self.assertRaises(TypeError, self.phi4._operate, [1], "divide")

        self.assertRaises(TypeError, self.phi1._operate, 1, "product", False)
        self.assertRaises(TypeError, self.phi1._operate, 1, "divide", False)
        self.assertRaises(TypeError, self.phi1._operate, "1", "product", False)
        self.assertRaises(TypeError, self.phi1._operate, "1", "divide", False)
        self.assertRaises(TypeError, self.phi1._operate, self.phi2._pdf,
                          "product", False)
        self.assertRaises(TypeError, self.phi1._operate, self.phi2._pdf,
                          "divide", False)
        self.assertRaises(TypeError, self.phi1._operate, [1], "product", False)
        self.assertRaises(TypeError, self.phi1._operate, [1], "divide", False)

        self.assertRaises(TypeError, self.phi4._operate, 1, "product", False)
        self.assertRaises(TypeError, self.phi4._operate, 1, "divide", False)
        self.assertRaises(TypeError, self.phi4._operate, "1", "product", False)
        self.assertRaises(TypeError, self.phi4._operate, "1", "divide", False)
        self.assertRaises(TypeError, self.phi4._operate, self.phi2._pdf,
                          "product", False)
        self.assertRaises(TypeError, self.phi4._operate, self.phi2._pdf,
                          "divide", False)
        self.assertRaises(TypeError, self.phi4._operate, [1], "product", False)
        self.assertRaises(TypeError, self.phi4._operate, [1], "divide", False)

        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi2)
        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi3)
        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi4)
        self.assertRaises(ValueError, self.phi2.__truediv__, self.phi3)
        self.assertRaises(ValueError, self.phi2.__truediv__, self.phi4)

    def test_copy(self):
        copy1 = self.phi1.copy()
        copy2 = self.phi3.copy()

        copy4 = copy1.copy()
        copy5 = copy2.copy()

        self.assertEqual(copy1.variables, copy4.variables)
        self.assertEqual(copy1._pdf, copy4._pdf)
        self.assertEqual(copy2.variables, copy5.variables)
        self.assertEqual(copy2._pdf, copy5._pdf)

        copy1.variables = ["A", "B"]
        self.assertEqual(copy4.variables, self.phi1.variables)

        def pdf(a, b):
            return (a + b) / (a * a + b * b)

        copy1._pdf = pdf
        copy1_pdf = pdf
        self.assertEqual(copy4._pdf, self.phi1._pdf)
        copy4.variables = ["X", "Y"]
        self.assertEqual(copy1.variables, ["A", "B"])
        copy4._pdf = lambda a, b: a + b
        for inp in np.random.rand(4, 2):
            self.assertEqual(copy1._pdf(inp[0], inp[1]),
                             copy1_pdf(inp[0], inp[1]))

        copy2.reduce([("x", 7.7)])

        def reduced_pdf(y, z):
            return z * (np.power(7.7, 1) * np.power(y, 2)) / beta(7.7, y)

        self.assertEqual(copy5.variables, self.phi3.variables)
        self.assertEqual(copy5._pdf, self.phi3._pdf)
        copy5.reduce([("x", 11), ("z", 13)])
        self.assertEqual(copy2.variables, ["y", "z"])
        for inp in np.random.rand(4, 2):
            self.assertEqual(copy2._pdf(inp[0], inp[1]),
                             reduced_pdf(inp[0], inp[1]))

    def tearDown(self):
        del self.phi1
        del self.phi2
        del self.phi3
class ContinuousFactor(BaseFactor):
    """
    Base class for factors representing various multivariate
    representations.
    """
    def __init__(self, variables, pdf, *args, **kwargs):
        """
        Parameters
        ----------
        variables: list or array-like
            The variables for wich the distribution is defined.

        pdf: function
            The probability density function of the distribution.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable dirichlet distribution with alpha = (1,2)
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.scope()
        ['x', 'y']
        >>> dirichlet_factor.assignment(5,6)
        226800.0
        """
        if not isinstance(variables, (list, tuple, np.ndarray)):
            raise TypeError(
                "variables: Expected type list or array-like, "
                "got type {var_type}".format(var_type=type(variables)))

        if len(set(variables)) != len(variables):
            raise ValueError("Variable names cannot be same.")

        variables = list(variables)

        if isinstance(pdf, str):
            if pdf == 'gaussian':
                self.distribution = GaussianDistribution(
                    variables=variables,
                    mean=kwargs['mean'],
                    covariance=kwargs['covariance'])
            else:
                raise NotImplementedError(
                    "{dist} distribution not supported.",
                    "Please use CustomDistribution".format(dist=pdf))

        elif isinstance(pdf, CustomDistribution):
            self.distribution = pdf

        elif callable(pdf):
            self.distribution = CustomDistribution(variables=variables,
                                                   distribution=pdf)

        else:
            raise ValueError(
                "pdf: Expected type: str or function, ",
                "Got: {instance}".format(instance=type(variables)))

    @property
    def pdf(self):
        """
        Returns the pdf of the ContinuousFactor.
        """
        return self.distribution.pdf

    @property
    def variable(self):
        return self.scope()[0]

    def scope(self):
        """
        Returns the scope of the factor.

        Returns
        -------
        list: List of variable names in the scope of the factor.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> normal_pdf = lambda x: multivariate_normal(x, [0, 0], [[1, 0], [0, 1]])
        >>> phi = ContinuousFactor(['x1', 'x2'], normal_pdf)
        >>> phi.scope()
        ['x1', 'x2']
        """
        return self.distribution.variables

    def get_evidence(self):
        return self.scope()[1:]

    def assignment(self, *args):
        """
        Returns a list of pdf assignments for the corresponding values.

        Parameters
        ----------
        *args: values
            Values whose assignment is to be computed.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> normal_pdf = lambda x1, x2: multivariate_normal.pdf((x1, x2), [0, 0], [[1, 0], [0, 1]])
        >>> phi = ContinuousFactor(['x1', 'x2'], normal_pdf)
        >>> phi.assignment(1, 2)
        0.013064233284684921
        """
        return self.distribution.assignment(*args)

    def copy(self):
        """
        Return a copy of the distribution.

        Returns
        -------
        ContinuousFactor object: copy of the distribution

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable dirichlet distribution with alpha = (1,2)
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.variables
        ['x', 'y']
        >>> copy_factor = dirichlet_factor.copy()
        >>> copy_factor.variables
        ['x', 'y']
        """
        return ContinuousFactor(self.scope(), self.distribution.copy())

    def discretize(self, method, *args, **kwargs):
        """
        Discretizes the continuous distribution into discrete
        probability masses using various methods.

        Parameters
        ----------
        method : A Discretizer Class from pgmpy.discretize

        *args, **kwargs:
            The parameters to be given to the Discretizer Class.

        Returns
        -------
        An n-D array or a DiscreteFactor object according to the discretiztion
        method used.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from pgmpy.factors.continuous import RoundingDiscretizer
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.discretize(RoundingDiscretizer, low=1, high=2, cardinality=5)
        # TODO: finish this
        """
        return method(self, *args, **kwargs).get_discrete_values()

    def reduce(self, values, inplace=True):
        """
        Reduces the factor to the context of the given variable values.

        Parameters
        ----------
        values: list, array-like
            A list of tuples of the form (variable_name, variable_value).

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new ContinuosFactor object.

        Returns
        -------
        ContinuousFactor or None: if inplace=True (default) returns None
                                  if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> def custom_pdf(x, y, z):
        ...     return z*(np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> custom_factor = ContinuousFactor(['x', 'y', 'z'], custom_pdf)
        >>> custom_factor.variables
        ['x', 'y', 'z']
        >>> custom_factor.assignment(1, 2, 3)
        24.0

        >>> custom_factor.reduce([('y', 2)])
        >>> custom_factor.variables
        ['x', 'z']
        >>> custom_factor.assignment(1, 3)
        24.0
        """
        phi = self if inplace else self.copy()

        phi.distribution = phi.distribution.reduce(values, inplace=False)
        if not inplace:
            return phi

    def marginalize(self, variables, inplace=True):
        """
        Marginalize the factor with respect to the given variables.

        Parameters
        ----------
        variables: list, array-like
            List of variables with respect to which factor is to be maximized.

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new ContinuousFactor instance.

        Returns
        -------
        DiscreteFactor or None: if inplace=True (default) returns None
                        if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> std_normal_pdf = lambda *x: multivariate_normal.pdf(x, [0, 0], [[1, 0], [0, 1]])
        >>> std_normal = ContinuousFactor(['x1', 'x2'], std_normal_pdf)
        >>> std_normal.scope()
        ['x1', 'x2']
        >>> std_normal.assignment([1, 1])
        0.058549831524319168
        >>> std_normal.marginalize(['x2'])
        >>> std_normal.scope()
        ['x1']
        >>> std_normal.assignment(1)

        """
        phi = self if inplace else self.copy()
        phi.distribution = phi.distribution.marginalize(variables,
                                                        inplace=False)

        if not inplace:
            return phi

    def normalize(self, inplace=True):
        """
        Normalizes the pdf of the continuous factor so that it integrates to
        1 over all the variables.

        Parameters
        ----------
        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new factor.

        Returns
        -------
        ContinuousFactor or None:
             if inplace=True (default) returns None
             if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> std_normal_pdf = lambda x: 2 * multivariate_normal.pdf(x, [0, 0], [[1, 0], [0, 1]])
        >>> std_normal = ContinuousFactor(['x1', 'x2'], std_normal_pdf)
        >>> std_normal.assignment(1, 1)
        0.117099663049
        >>> std_normal.normalize()
        >>> std_normal.assignment(1, 1)
        0.0585498315243

        """
        phi = self if inplace else self.copy()
        phi.distriution = phi.distribution.normalize(inplace=False)

        if not inplace:
            return phi

    def is_valid_cpd(self):
        return self.distribution.is_valid_cpd()

    def _operate(self, other, operation, inplace=True):
        """
        Gives the ContinuousFactor operation (product or divide) with
        the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        operation: String
            'product' for multiplication operation and 'divide' for
            division operation.

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new factor.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `DiscreteFactor` instance.

        """
        if not isinstance(other, ContinuousFactor):
            raise TypeError(
                "ContinuousFactor objects can only be multiplied ",
                "or divided with another ContinuousFactor object. ",
                "Got {other_type}, expected: ContinuousFactor.".format(
                    other_type=type(other)))

        phi = self if inplace else self.copy()
        phi.distribution = phi.distribution._operate(other=other.distribution,
                                                     operation=operation,
                                                     inplace=False)

        if not inplace:
            return phi

    def product(self, other, inplace=True):
        """
        Gives the ContinuousFactor product with the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `ContinuousFactor` instance.

        Example
        -------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> sn_pdf1 = lambda x: multivariate_normal.pdf([x], [0], [[1]])
        >>> sn_pdf2 = lambda x1,x2: multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])
        >>> sn1 = ContinuousFactor(['x2'], sn_pdf1)
        >>> sn2 = ContinuousFactor(['x1', 'x2'], sn_pdf2)

        >>> sn3 = sn1.product(sn2, inplace=False)
        >>> sn3.assignment(0, 0)
        0.063493635934240983

        >>> sn3 = sn1 * sn2
        >>> sn3.assignment(0, 0)
        0.063493635934240983
        """
        return self._operate(other, 'product', inplace)

    def divide(self, other, inplace=True):
        """
        Gives the ContinuousFactor divide with the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `DiscreteFactor` instance.

        Example
        -------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> sn_pdf1 = lambda x: multivariate_normal.pdf([x], [0], [[1]])
        >>> sn_pdf2 = lambda x1,x2: multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])
        >>> sn1 = ContinuousFactor(['x2'], sn_pdf1)
        >>> sn2 = ContinuousFactor(['x1', 'x2'], sn_pdf2)

        >>> sn4 = sn2.divide(sn1, inplace=False)
        >>> sn4.assignment(0, 0)
        0.3989422804014327

        >>> sn4 = sn2 / sn1
        >>> sn4.assignment(0, 0)
        0.3989422804014327
        """
        if set(other.scope()) - set(self.scope()):
            raise ValueError("Scope of divisor should be a subset of dividend")

        return self._operate(other, 'divide', inplace)

    def __mul__(self, other):
        return self.product(other, inplace=False)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        return self.divide(other, inplace=False)

    __div__ = __truediv__
class ContinuousFactor(BaseFactor):
    """
    Base class for factors representing various multivariate
    representations.
    """
    def __init__(self, variables, pdf, *args, **kwargs):
        """
        Parameters
        ----------
        variables: list or array-like
            The variables for wich the distribution is defined.

        pdf: function
            The probability density function of the distribution.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable drichlet ditribution with alpha = (1,2)
        >>> def drichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], drichlet_pdf)
        >>> dirichlet_factor.scope()
        ['x', 'y']
        >>> dirichlet_factor.assignment(5,6)
        226800.0
        """
        if not isinstance(variables, (list, tuple, np.ndarray)):
            raise TypeError("variables: Expected type list or array-like, "
                            "got type {var_type}".format(var_type=type(variables)))

        if len(set(variables)) != len(variables):
            raise ValueError("Variable names cannot be same.")

        variables = list(variables)

        if isinstance(pdf, str):
            if pdf == 'gaussian':
                self.distribution = GaussianDistribution(
                    variables=variables,
                    mean=kwargs['mean'],
                    covariance=kwargs['covariance'])
            else:
                raise NotImplementedError("{dist} distribution not supported.",
                                          "Please use CustomDistribution".
                                          format(dist=pdf))

        elif isinstance(pdf, CustomDistribution):
            self.distribution = pdf

        elif callable(pdf):
            self.distribution = CustomDistribution(
                variables=variables,
                distribution=pdf)

        else:
            raise ValueError("pdf: Expected type: str or function, ",
                             "Got: {instance}".format(instance=type(variables)))

    @property
    def pdf(self):
        """
        Returns the pdf of the ContinuousFactor.
        """
        return self.distribution.pdf

    def scope(self):
        """
        Returns the scope of the factor.

        Returns
        -------
        list: List of variable names in the scope of the factor.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> normal_pdf = lambda x: multivariate_normal(x, [0, 0], [[1, 0], [0, 1]])
        >>> phi = ContinuousFactor(['x1', 'x2'], normal_pdf)
        >>> phi.scope()
        ['x1', 'x2']
        """
        return self.distribution.variables

    def assignment(self, *args):
        """
        Returns a list of pdf assignments for the corresponding values.

        Parameters
        ----------
        *args: values
            Values whose assignment is to be computed.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> normal_pdf = lambda x1, x2: multivariate_normal.pdf((x1, x2), [0, 0], [[1, 0], [0, 1]])
        >>> phi = ContinuousFactor(['x1', 'x2'], normal_pdf)
        >>> phi.assignment(1, 2)
        0.013064233284684921
        """
        return self.distribution.assignment(*args)

    def copy(self):
        """
        Return a copy of the distribution.

        Returns
        -------
        ContinuousFactor object: copy of the distribution

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        # Two variable drichlet ditribution with alpha = (1,2)
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.variables
        ['x', 'y']
        >>> copy_factor = dirichlet_factor.copy()
        >>> copy_factor.variables
        ['x', 'y']
        """
        return ContinuousFactor(self.scope(), self.distribution.copy())

    def discretize(self, method, *args, **kwargs):
        """
        Discretizes the continuous distribution into discrete
        probability masses using various methods.

        Parameters
        ----------
        method : A Discretizer Class from pgmpy.discretize

        *args, **kwargs:
            The parameters to be given to the Discretizer Class.

        Returns
        -------
        An n-D array or a DiscreteFactor object according to the discretiztion
        method used.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from pgmpy.factors.continuous import RoundingDiscretizer
        >>> def dirichlet_pdf(x, y):
        ...     return (np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> dirichlet_factor = ContinuousFactor(['x', 'y'], dirichlet_pdf)
        >>> dirichlet_factor.discretize(RoundingDiscretizer, low=1, high=2, cardinality=5)
        # TODO: finish this
        """
        return method(self, *args, **kwargs).get_discrete_values()

    def reduce(self, values, inplace=True):
        """
        Reduces the factor to the context of the given variable values.

        Parameters
        ----------
        values: list, array-like
            A list of tuples of the form (variable_name, variable_value).

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new ContinuosFactor object.

        Returns
        -------
        ContinuousFactor or None: if inplace=True (default) returns None
                                  if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> import numpy as np
        >>> from scipy.special import beta
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> def custom_pdf(x, y, z):
        ...     return z*(np.power(x, 1) * np.power(y, 2)) / beta(x, y)
        >>> custom_factor = ContinuousFactor(['x', 'y', 'z'], custom_pdf)
        >>> custom_factor.variables
        ['x', 'y', 'z']
        >>> custom_factor.assignment(1, 2, 3)
        24.0

        >>> custom_factor.reduce([('y', 2)])
        >>> custom_factor.variables
        ['x', 'z']
        >>> custom_factor.assignment(1, 3)
        24.0
        """
        phi = self if inplace else self.copy()

        phi.distribution = phi.distribution.reduce(values, inplace=False)
        if not inplace:
            return phi

    def marginalize(self, variables, inplace=True):
        """
        Marginalize the factor with respect to the given variables.

        Parameters
        ----------
        variables: list, array-like
            List of variables with respect to which factor is to be maximized.

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new ContinuousFactor instance.

        Returns
        -------
        DiscreteFactor or None: if inplace=True (default) returns None
                        if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> std_normal_pdf = lambda *x: multivariate_normal.pdf(x, [0, 0], [[1, 0], [0, 1]])
        >>> std_normal = ContinuousFactor(['x1', 'x2'], std_normal_pdf)
        >>> std_normal.scope()
        ['x1', 'x2']
        >>> std_normal.assignment([1, 1])
        0.058549831524319168
        >>> std_normal.marginalize(['x2'])
        >>> std_normal.scope()
        ['x1']
        >>> std_normal.assignment(1)

        """
        phi = self if inplace else self.copy()
        phi.distribution = phi.distribution.marginalize(variables, inplace=False)

        if not inplace:
            return phi

    def normalize(self, inplace=True):
        """
        Normalizes the pdf of the continuous factor so that it integrates to
        1 over all the variables.

        Parameters
        ----------
        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new factor.

        Returns
        -------
        ContinuousFactor or None:
             if inplace=True (default) returns None
             if inplace=False returns a new ContinuousFactor instance.

        Examples
        --------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> std_normal_pdf = lambda x: 2 * multivariate_normal.pdf(x, [0, 0], [[1, 0], [0, 1]])
        >>> std_normal = ContinuousFactor(['x1', 'x2'], std_normal_pdf)
        >>> std_normal.assignment(1, 1)
        0.117099663049
        >>> std_normal.normalize()
        >>> std_normal.assignment(1, 1)
        0.0585498315243

        """
        phi = self if inplace else self.copy()
        phi.distriution = phi.distribution.normalize(inplace=False)

        if not inplace:
            return phi

    def _operate(self, other, operation, inplace=True):
        """
        Gives the ContinuousFactor operation (product or divide) with
        the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        operation: String
            'product' for multiplication operation and 'divide' for
            division operation.

        inplace: boolean
            If inplace=True it will modify the factor itself, else would return
            a new factor.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `DiscreteFactor` instance.

        """
        if not isinstance(other, ContinuousFactor):
            raise TypeError("ContinuousFactor objects can only be multiplied ",
                            "or divided with another ContinuousFactor object. ",
                            "Got {other_type}, expected: ContinuousFactor.".format(
                                other_type=type(other)))

        phi = self if inplace else self.copy()
        phi.distribution = phi.distribution._operate(
            other=other.distribution, operation=operation, inplace=False)

        if not inplace:
            return phi

    def product(self, other, inplace=True):
        """
        Gives the ContinuousFactor product with the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `ContinuousFactor` instance.

        Example
        -------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> sn_pdf1 = lambda x: multivariate_normal.pdf([x], [0], [[1]])
        >>> sn_pdf2 = lambda x1,x2: multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])
        >>> sn1 = ContinuousFactor(['x2'], sn_pdf1)
        >>> sn2 = ContinuousFactor(['x1', 'x2'], sn_pdf2)

        >>> sn3 = sn1.product(sn2, inplace=False)
        >>> sn3.assignment(0, 0)
        0.063493635934240983

        >>> sn3 = sn1 * sn2
        >>> sn3.assignment(0, 0)
        0.063493635934240983
        """
        return self._operate(other, 'product', inplace)

    def divide(self, other, inplace=True):
        """
        Gives the ContinuousFactor divide with the other factor.

        Parameters
        ----------
        other: ContinuousFactor
            The ContinuousFactor to be multiplied.

        Returns
        -------
        ContinuousFactor or None:
                        if inplace=True (default) returns None
                        if inplace=False returns a new `DiscreteFactor` instance.

        Example
        -------
        >>> from pgmpy.factors.continuous import ContinuousFactor
        >>> from scipy.stats import multivariate_normal
        >>> sn_pdf1 = lambda x: multivariate_normal.pdf([x], [0], [[1]])
        >>> sn_pdf2 = lambda x1,x2: multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])
        >>> sn1 = ContinuousFactor(['x2'], sn_pdf1)
        >>> sn2 = ContinuousFactor(['x1', 'x2'], sn_pdf2)

        >>> sn4 = sn2.divide(sn1, inplace=False)
        >>> sn4.assignment(0, 0)
        0.3989422804014327

        >>> sn4 = sn2 / sn1
        >>> sn4.assignment(0, 0)
        0.3989422804014327
        """
        if set(other.scope()) - set(self.scope()):
            raise ValueError("Scope of divisor should be a subset of dividend")

        return self._operate(other, 'divide', inplace)

    def __mul__(self, other):
        return self.product(other, inplace=False)

    def __rmul__(self, other):
        return self.__mul__(other)

    def __truediv__(self, other):
        return self.divide(other, inplace=False)

    __div__ = __truediv__
Esempio n. 11
0
 def setUp(self):
     self.phi1 = CustomDistribution(['x', 'y'], self.pdf1)
     self.phi2 = CustomDistribution(['x1', 'x2'], self.pdf2)
     self.phi3 = CustomDistribution(['x', 'y', 'z'], self.pdf3)
     self.phi4 = CustomDistribution(['x1', 'x2', 'x3'], self.pdf4)
Esempio n. 12
0
class TestCustomDistributionMethods(unittest.TestCase):
    def pdf1(self, x, y):
        return np.power(x, 1) * np.power(y, 2) / beta(x, y)

    def pdf2(self, x1, x2):
        return multivariate_normal.pdf([x1, x2], [0, 0], [[1, 0], [0, 1]])

    def pdf3(self, x, y, z):
        return z * (np.power(x, 1) * np.power(y, 2)) / beta(x, y)

    def pdf4(self, x1, x2, x3):
        return multivariate_normal.pdf([x1, x2, x3], [0, 0, 0],
                                       [[1, 0, 0], [0, 1, 0], [0, 0, 1]])

    def setUp(self):
        self.phi1 = CustomDistribution(['x', 'y'], self.pdf1)
        self.phi2 = CustomDistribution(['x1', 'x2'], self.pdf2)
        self.phi3 = CustomDistribution(['x', 'y', 'z'], self.pdf3)
        self.phi4 = CustomDistribution(['x1', 'x2', 'x3'], self.pdf4)

    def test_variables(self):
        self.assertEqual(self.phi1.variables, self.phi1._variables)
        self.assertEqual(self.phi2.variables, self.phi2._variables)
        self.assertEqual(self.phi3.variables, self.phi3._variables)

    def test_assignment(self):
        self.assertEqual(self.phi1.assignment(1.212, 2), self.pdf1(1.212, 2))
        self.assertEqual(self.phi2.assignment(1, -2.231), self.pdf2(1, -2.231))
        self.assertEqual(self.phi3.assignment(1.212, 2.213, -3), self.pdf3(1.212, 2.213, -3))

    def test_reduce(self):
        phi1 = self.phi1.copy()
        phi1.reduce([('x', 1)])
        reduced_pdf1 = lambda y: (np.power(1, 1) * np.power(y, 2))/beta(1, y)
        self.assertEqual(phi1.variables, ['y'])
        for inp in np.random.rand(4):
            self.assertEqual(phi1._pdf(inp), reduced_pdf1(inp))
            self.assertEqual(phi1._pdf(y=inp), reduced_pdf1(inp))

        phi1 = self.phi1.reduce([('x', 1)], inplace=False)
        self.assertEqual(phi1.variables, ['y'])
        for inp in np.random.rand(4):
            self.assertEqual(phi1._pdf(inp), reduced_pdf1(inp))
            self.assertEqual(phi1._pdf(y=inp), reduced_pdf1(inp))

        phi2 = self.phi2.copy()
        phi2.reduce([('x2', 7.213)])
        reduced_pdf2 = lambda x1: multivariate_normal.pdf([x1, 7.213], [0, 0], [[1, 0], [0, 1]])
        self.assertEqual(phi2.variables, ['x1'])
        for inp in np.random.rand(4):
            self.assertEqual(phi2._pdf(inp), reduced_pdf2(inp))
            self.assertEqual(phi2._pdf(x1=inp), reduced_pdf2(inp))

        phi2 = self.phi2.reduce([('x2', 7.213)], inplace=False)
        self.assertEqual(phi2.variables, ['x1'])
        for inp in np.random.rand(4):
            self.assertEqual(phi2._pdf(inp), reduced_pdf2(inp))
            self.assertEqual(phi2._pdf(x1=inp), reduced_pdf2(inp))

        phi3 = self.phi3.copy()
        phi3.reduce([('y', 0.112), ('z', 23)])
        reduced_pdf4 = lambda x: 23*(np.power(x, 1)*np.power(0.112, 2))/beta(x, 0.112)
        self.assertEqual(phi3.variables, ['x'])
        for inp in np.random.rand(4):
            self.assertEqual(phi3._pdf(inp), reduced_pdf4(inp))
            self.assertEqual(phi3._pdf(x=inp), reduced_pdf4(inp))

        phi3 = self.phi3.copy()
        phi3.reduce([('y', 0.112)])
        reduced_pdf3 = lambda x, z: z*(np.power(x, 1)*np.power(0.112, 2))/beta(x, 0.112)
        self.assertEqual(phi3.variables, ['x', 'z'])
        for inp in np.random.rand(4, 2):
            self.assertEqual(phi3._pdf(inp[0], inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(x=inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))

        phi3 = self.phi3.reduce([('y', 0.112)], inplace=False)
        self.assertEqual(phi3.variables, ['x', 'z'])
        for inp in np.random.rand(4, 2):
            self.assertEqual(phi3._pdf(inp[0], inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(x=inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))
            self.assertEqual(phi3._pdf(inp[0], z=inp[1]),
                             reduced_pdf3(inp[0], inp[1]))

        phi3 = self.phi3.reduce([('y', 0.112), ('z', 23)], inplace=False)
        self.assertEqual(phi3.variables, ['x'])
        for inp in np.random.rand(4):
            self.assertEqual(phi3._pdf(inp), reduced_pdf4(inp))
            self.assertEqual(phi3._pdf(x=inp), reduced_pdf4(inp))

    def test_reduce_error(self):
        self.assertRaises(TypeError, self.phi1.reduce, 'x1')
        self.assertRaises(TypeError, self.phi1.reduce, set(['x', 'y']))
        self.assertRaises(TypeError, self.phi1.reduce, {'x': 1, 'y': 1})

        self.assertRaises(TypeError, self.phi4.reduce, 'x4')
        self.assertRaises(TypeError, self.phi4.reduce, set(['x1', 'x2', 'x3']))
        self.assertRaises(TypeError, self.phi4.reduce, {'x1': 1, 'x2': 1, 'x3': 1})

        self.assertRaises(ValueError, self.phi1.reduce, [('z', 3)])
        self.assertRaises(ValueError, self.phi1.reduce, [('x', 0), ('y', 1), ('z', 4)])

        self.assertRaises(ValueError, self.phi4.reduce, [('x4', 7)])
        self.assertRaises(ValueError, self.phi4.reduce, [('x1', 1), ('x2', 2), ('x3', 3), ('x4', 4)])

    def test_marginalize(self):
        phi2 = self.phi2.copy()
        phi2.marginalize(['x2'])
        self.assertEqual(phi2.variables, ['x1'])
        for inp in np.random.rand(4):
            np_test.assert_almost_equal(phi2._pdf(inp),
                                        multivariate_normal.pdf([inp], [0], [[1]]))

        phi2 = self.phi2.marginalize(['x2'], inplace=False)
        self.assertEqual(phi2.variables, ['x1'])
        for inp in np.random.rand(4):
            np_test.assert_almost_equal(phi2._pdf(inp),
                                        multivariate_normal.pdf([inp], [0], [[1]]))

        phi4 = self.phi4.copy()
        phi4.marginalize(['x2'])

        self.assertEqual(phi4.variables, ['x1', 'x3'])
        for inp in np.random.rand(4, 2):
            np_test.assert_almost_equal(
                phi4._pdf(inp[0], inp[1]), multivariate_normal.pdf(
                    [inp[0], inp[1]], [0, 0], [[1, 0], [0, 1]]))

        phi4.marginalize(['x3'])
        self.assertEqual(phi4.variables, ['x1'])
        for inp in np.random.rand(1):
            np_test.assert_almost_equal(phi4._pdf(inp),
                                        multivariate_normal.pdf([inp], [0], [[1]]))

        phi4 = self.phi4.marginalize(['x2'], inplace=False)
        self.assertEqual(phi4.variables, ['x1', 'x3'])
        for inp in np.random.rand(4, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        multivariate_normal.pdf([inp[0], inp[1]], [0, 0], [[1, 0], [0, 1]]))

        phi4 = phi4.marginalize(['x3'], inplace=False)
        self.assertEqual(phi4.variables, ['x1'])
        for inp in np.random.rand(1):
            np_test.assert_almost_equal(phi4._pdf(inp),
                                        multivariate_normal.pdf([inp], [0], [[1]]))

    def test_marginalize_error(self):
        self.assertRaises(TypeError, self.phi1.marginalize, 'x1')
        self.assertRaises(TypeError, self.phi1.marginalize, set(['x', 'y']))
        self.assertRaises(TypeError, self.phi1.marginalize, {'x': 1, 'y': 1})

        self.assertRaises(TypeError, self.phi4.marginalize, 'x4')
        self.assertRaises(TypeError, self.phi4.marginalize, set(['x1', 'x2', 'x3']))
        self.assertRaises(TypeError, self.phi4.marginalize, {'x1': 1, 'x2': 1, 'x3': 1})

        self.assertRaises(ValueError, self.phi1.marginalize, ['z'])
        self.assertRaises(ValueError, self.phi1.marginalize, ['x', 'y', 'z'])

        self.assertRaises(ValueError, self.phi4.marginalize, ['x4'])
        self.assertRaises(ValueError, self.phi4.marginalize, ['x1', 'x2', 'x3', 'x4'])

    def test_normalize(self):
        def pdf2(x1, x2):
            return 2 * self.pdf2(x1, x2)

        phi2 = CustomDistribution(['x1', 'x2'], pdf2)
        phi4 = phi2.copy()

        phi4.normalize()
        self.assertEqual(phi4.variables, phi2.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

        phi4 = phi2.normalize(inplace=False)
        self.assertEqual(phi4.variables, phi4.variables)
        for inp in np.random.rand(1, 2):
            np_test.assert_almost_equal(phi4._pdf(inp[0], inp[1]),
                                        self.pdf2(inp[0], inp[1]))

    def test_operate(self):
        phi1 = self.phi1.copy()
        phi1._operate(self.phi2, 'product')
        self.assertEqual(phi1.variables, ['x', 'y', 'x1', 'x2'])
        for inp in np.random.rand(4, 4):
            self.assertEqual(phi1._pdf(*inp), self.phi1._pdf(inp[0], inp[1])
                             * self.phi2._pdf(inp[2], inp[3]))

        phi1 = self.phi1._operate(self.phi2, 'product', inplace=False)
        self.assertEqual(phi1.variables, ['x', 'y', 'x1', 'x2'])
        for inp in np.random.rand(4, 4):
            self.assertEqual(phi1._pdf(*inp), self.phi1._pdf(inp[0], inp[1])
                             * self.phi2._pdf(inp[2], inp[3]))

        phi1 = self.phi1 * self.phi2
        self.assertEqual(phi1.variables, ['x', 'y', 'x1', 'x2'])
        for inp in np.random.rand(4, 4):
            self.assertEqual(phi1._pdf(*inp), self.phi1._pdf(inp[0], inp[1])
                             * self.phi2._pdf(inp[2], inp[3]))

        phi3 = self.phi3.copy()
        phi3._operate(self.phi1, 'product')
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) *
                             self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3._operate(self.phi1, 'product', inplace=False)
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) *
                             self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3 * self.phi1
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) *
                             self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3.copy()
        phi3._operate(self.phi1, 'divide')
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) /
                             self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3._operate(self.phi1, 'divide', inplace=False)
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) /
                             self.phi1._pdf(inp[0], inp[1]))

        phi3 = self.phi3 / self.phi1
        self.assertEqual(phi3.variables, ['x', 'y', 'z'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi3._pdf(*inp), self.phi3._pdf(*inp) /
                             self.phi1._pdf(inp[0], inp[1]))

        phi4 = self.phi4.copy()
        phi4._operate(self.phi2, 'product')
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) *
                             self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4._operate(self.phi2, 'product', inplace=False)
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) *
                             self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4 * self.phi2
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) *
                             self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4.copy()
        phi4._operate(self.phi2, 'divide')
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) /
                             self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4._operate(self.phi2, 'divide', inplace=False)
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) /
                             self.phi2._pdf(inp[0], inp[1]))

        phi4 = self.phi4 / self.phi2
        self.assertEqual(phi4.variables, ['x1', 'x2', 'x3'])
        for inp in np.random.rand(4, 3):
            self.assertEqual(phi4._pdf(*inp), self.phi4._pdf(*inp) /
                             self.phi2._pdf(inp[0], inp[1]))

    def test_operate_error(self):
        self.assertRaises(TypeError, self.phi1._operate, 1, 'product')
        self.assertRaises(TypeError, self.phi1._operate, 1, 'divide')
        self.assertRaises(TypeError, self.phi1._operate, '1', 'product')
        self.assertRaises(TypeError, self.phi1._operate, '1', 'divide')
        self.assertRaises(TypeError, self.phi1._operate,
                          self.phi2._pdf, 'product')
        self.assertRaises(TypeError, self.phi1._operate,
                          self.phi2._pdf, 'divide')
        self.assertRaises(TypeError, self.phi1._operate, [1], 'product')
        self.assertRaises(TypeError, self.phi1._operate, [1], 'divide')

        self.assertRaises(TypeError, self.phi4._operate, 1, 'product')
        self.assertRaises(TypeError, self.phi4._operate, 1, 'divide')
        self.assertRaises(TypeError, self.phi4._operate, '1', 'product')
        self.assertRaises(TypeError, self.phi4._operate, '1', 'divide')
        self.assertRaises(TypeError, self.phi4._operate,
                          self.phi2._pdf, 'product')
        self.assertRaises(TypeError, self.phi4._operate,
                          self.phi2._pdf, 'divide')
        self.assertRaises(TypeError, self.phi4._operate, [1], 'product')
        self.assertRaises(TypeError, self.phi4._operate, [1], 'divide')

        self.assertRaises(TypeError, self.phi1._operate, 1, 'product', False)
        self.assertRaises(TypeError, self.phi1._operate, 1, 'divide', False)
        self.assertRaises(TypeError, self.phi1._operate, '1', 'product', False)
        self.assertRaises(TypeError, self.phi1._operate, '1', 'divide', False)
        self.assertRaises(TypeError, self.phi1._operate,
                          self.phi2._pdf, 'product', False)
        self.assertRaises(TypeError, self.phi1._operate,
                          self.phi2._pdf, 'divide', False)
        self.assertRaises(TypeError, self.phi1._operate, [1], 'product', False)
        self.assertRaises(TypeError, self.phi1._operate, [1], 'divide', False)

        self.assertRaises(TypeError, self.phi4._operate, 1, 'product', False)
        self.assertRaises(TypeError, self.phi4._operate, 1, 'divide', False)
        self.assertRaises(TypeError, self.phi4._operate, '1', 'product', False)
        self.assertRaises(TypeError, self.phi4._operate, '1', 'divide', False)
        self.assertRaises(TypeError, self.phi4._operate,
                          self.phi2._pdf, 'product', False)
        self.assertRaises(TypeError, self.phi4._operate,
                          self.phi2._pdf, 'divide', False)
        self.assertRaises(TypeError, self.phi4._operate, [1], 'product', False)
        self.assertRaises(TypeError, self.phi4._operate, [1], 'divide', False)

        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi2)
        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi3)
        self.assertRaises(ValueError, self.phi1.__truediv__, self.phi4)
        self.assertRaises(ValueError, self.phi2.__truediv__, self.phi3)
        self.assertRaises(ValueError, self.phi2.__truediv__, self.phi4)

    def test_copy(self):
        copy1 = self.phi1.copy()
        copy2 = self.phi3.copy()

        copy4 = copy1.copy()
        copy5 = copy2.copy()

        self.assertEqual(copy1.variables, copy4.variables)
        self.assertEqual(copy1._pdf, copy4._pdf)
        self.assertEqual(copy2.variables, copy5.variables)
        self.assertEqual(copy2._pdf, copy5._pdf)

        copy1.variables = ['A', 'B']
        self.assertEqual(copy4.variables, self.phi1.variables)

        def pdf(a, b):
            return (a + b) / (a * a + b * b)
        copy1._pdf = pdf
        copy1_pdf = pdf
        self.assertEqual(copy4._pdf, self.phi1._pdf)
        copy4.variables = ['X', 'Y']
        self.assertEqual(copy1.variables, ['A', 'B'])
        copy4._pdf = lambda a, b: a + b
        for inp in np.random.rand(4, 2):
            self.assertEqual(copy1._pdf(inp[0], inp[1]),
                             copy1_pdf(inp[0], inp[1]))

        copy2.reduce([('x', 7.7)])

        def reduced_pdf(y, z):
            return z*(np.power(7.7, 1) * np.power(y, 2)) / beta(7.7, y)
        self.assertEqual(copy5.variables, self.phi3.variables)
        self.assertEqual(copy5._pdf, self.phi3._pdf)
        copy5.reduce([('x', 11), ('z', 13)])
        self.assertEqual(copy2.variables, ['y', 'z'])
        for inp in np.random.rand(4, 2):
            self.assertEqual(copy2._pdf(inp[0], inp[1]),
                             reduced_pdf(inp[0], inp[1]))

    def tearDown(self):
        del self.phi1
        del self.phi2
        del self.phi3