Ejemplo n.º 1
0
class TestUndirectedGraphTriangulation(unittest.TestCase):
    def setUp(self):
        self.graph = MarkovModel()

    def test_check_clique(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "a")])
        self.assertTrue(self.graph.is_clique(["a", "b", "c"]))

    def test_is_triangulated(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "a")])
        self.assertTrue(self.graph.is_triangulated())

    def test_triangulation_h1_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H1", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["c", "d"]],
        )

    def test_triangulation_h2_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H2", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["c", "d"]],
        )

    def test_triangulation_h3_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H3", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h4_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H4", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h5_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H4", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h6_inplace(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic="H4", inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_cardinality_mismatch_raises_error(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        factor_list = [
            DiscreteFactor(edge, [2, 2], np.random.rand(4))
            for edge in self.graph.edges()
        ]
        self.graph.add_factors(*factor_list)
        self.graph.add_factors(
            DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6)))
        self.assertRaises(ValueError, self.graph.triangulate)

    def test_triangulation_h1_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H1", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["c", "d"]],
        )

    def test_triangulation_h2_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H2", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "c"], ["a", "d"], ["b", "c"], ["c", "d"]],
        )

    def test_triangulation_h3_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H3", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h4_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H4", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h5_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H5", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_triangulation_h6_create_new(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c"), ("c", "d"),
                                   ("d", "a")])
        phi1 = DiscreteFactor(["a", "b"], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(["b", "c"], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(["c", "d"], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(["d", "a"], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic="H6", inplace=True)
        self.assertListEqual(
            hf.recursive_sorted(H.edges()),
            [["a", "b"], ["a", "d"], ["b", "c"], ["b", "d"], ["c", "d"]],
        )

    def test_copy(self):
        # Setup the original graph
        self.graph.add_nodes_from(["a", "b"])
        self.graph.add_edges_from([("a", "b")])

        # Generate the copy
        copy = self.graph.copy()

        # Ensure the copied model is correct
        self.assertTrue(copy.check_model())

        # Basic sanity checks to ensure the graph was copied correctly
        self.assertEqual(len(copy.nodes()), 2)
        self.assertListEqual(list(copy.neighbors("a")), ["b"])
        self.assertListEqual(list(copy.neighbors("b")), ["a"])

        # Modify the original graph ...
        self.graph.add_nodes_from(["c"])
        self.graph.add_edges_from([("c", "b")])

        # ... and ensure none of those changes get propagated
        self.assertEqual(len(copy.nodes()), 2)
        self.assertListEqual(list(copy.neighbors("a")), ["b"])
        self.assertListEqual(list(copy.neighbors("b")), ["a"])
        with self.assertRaises(nx.NetworkXError):
            list(copy.neighbors("c"))

        # Ensure the copy has no factors at this point
        self.assertEqual(len(copy.get_factors()), 0)

        # Add factors to the original graph
        phi1 = DiscreteFactor(["a", "b"], [2, 2], [[0.3, 0.7], [0.9, 0.1]])
        self.graph.add_factors(phi1)

        # The factors should not get copied over
        with self.assertRaises(AssertionError):
            self.assertListEqual(list(copy.get_factors()),
                                 self.graph.get_factors())

        # Create a fresh copy
        del copy
        copy = self.graph.copy()
        self.assertListEqual(list(copy.get_factors()),
                             self.graph.get_factors())

        # If we change factors in the original, it should not be passed to the clone
        phi1.values = np.array([[0.5, 0.5], [0.5, 0.5]])
        self.assertNotEqual(self.graph.get_factors(), copy.get_factors())

        # Start with a fresh copy
        del copy
        self.graph.add_nodes_from(["d"])
        copy = self.graph.copy()

        # Ensure an unconnected node gets copied over as well
        self.assertEqual(len(copy.nodes()), 4)
        self.assertListEqual(list(self.graph.neighbors("a")), ["b"])
        self.assertTrue("a" in self.graph.neighbors("b"))
        self.assertTrue("c" in self.graph.neighbors("b"))
        self.assertListEqual(list(self.graph.neighbors("c")), ["b"])
        self.assertListEqual(list(self.graph.neighbors("d")), [])

        # Verify that changing the copied model should not update the original
        copy.add_nodes_from(["e"])
        self.assertListEqual(list(copy.neighbors("e")), [])
        with self.assertRaises(nx.NetworkXError):
            self.graph.neighbors("e")

        # Verify that changing edges in the copy doesn't create edges in the original
        copy.add_edges_from([("d", "b")])

        self.assertTrue("a" in copy.neighbors("b"))
        self.assertTrue("c" in copy.neighbors("b"))
        self.assertTrue("d" in copy.neighbors("b"))

        self.assertTrue("a" in self.graph.neighbors("b"))
        self.assertTrue("c" in self.graph.neighbors("b"))
        self.assertFalse("d" in self.graph.neighbors("b"))

        # If we remove factors from the copied model, it should not reflect in the original
        copy.remove_factors(phi1)
        self.assertEqual(len(self.graph.get_factors()), 1)
        self.assertEqual(len(copy.get_factors()), 0)

    def tearDown(self):
        del self.graph
Ejemplo n.º 2
0
class TestMarkovModelCreation(unittest.TestCase):
    def setUp(self):
        self.graph = MarkovModel()

    def test_class_init_without_data(self):
        self.assertIsInstance(self.graph, MarkovModel)

    def test_class_init_with_data_string(self):
        self.g = MarkovModel([("a", "b"), ("b", "c")])
        self.assertListEqual(sorted(self.g.nodes()), ["a", "b", "c"])
        self.assertListEqual(hf.recursive_sorted(self.g.edges()),
                             [["a", "b"], ["b", "c"]])

    def test_class_init_with_data_nonstring(self):
        self.g = MarkovModel([(1, 2), (2, 3)])

    def test_add_node_string(self):
        self.graph.add_node("a")
        self.assertListEqual(list(self.graph.nodes()), ["a"])

    def test_add_node_nonstring(self):
        self.graph.add_node(1)

    def test_add_nodes_from_string(self):
        self.graph.add_nodes_from(["a", "b", "c", "d"])
        self.assertListEqual(sorted(self.graph.nodes()), ["a", "b", "c", "d"])

    def test_add_nodes_from_non_string(self):
        self.graph.add_nodes_from([1, 2, 3, 4])

    def test_add_edge_string(self):
        self.graph.add_edge("d", "e")
        self.assertListEqual(sorted(self.graph.nodes()), ["d", "e"])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [["d", "e"]])
        self.graph.add_nodes_from(["a", "b", "c"])
        self.graph.add_edge("a", "b")
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [["a", "b"], ["d", "e"]])

    def test_add_edge_nonstring(self):
        self.graph.add_edge(1, 2)

    def test_add_edge_selfloop(self):
        self.assertRaises(ValueError, self.graph.add_edge, "a", "a")

    def test_add_edges_from_string(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c")])
        self.assertListEqual(sorted(self.graph.nodes()), ["a", "b", "c"])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [["a", "b"], ["b", "c"]])
        self.graph.add_nodes_from(["d", "e", "f"])
        self.graph.add_edges_from([("d", "e"), ("e", "f")])
        self.assertListEqual(sorted(self.graph.nodes()),
                             ["a", "b", "c", "d", "e", "f"])
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            hf.recursive_sorted([("a", "b"), ("b", "c"), ("d", "e"),
                                 ("e", "f")]),
        )

    def test_add_edges_from_nonstring(self):
        self.graph.add_edges_from([(1, 2), (2, 3)])

    def test_add_edges_from_self_loop(self):
        self.assertRaises(ValueError, self.graph.add_edges_from, [("a", "a")])

    def test_number_of_neighbors(self):
        self.graph.add_edges_from([("a", "b"), ("b", "c")])
        self.assertEqual(len(list(self.graph.neighbors("b"))), 2)

    def tearDown(self):
        del self.graph
Ejemplo n.º 3
0
class TestMarkovModelCreation(unittest.TestCase):
    def setUp(self):
        self.graph = MarkovModel()

    def test_class_init_without_data(self):
        self.assertIsInstance(self.graph, MarkovModel)

    def test_class_init_with_data_string(self):
        self.g = MarkovModel([('a', 'b'), ('b', 'c')])
        self.assertListEqual(sorted(self.g.nodes()), ['a', 'b', 'c'])
        self.assertListEqual(hf.recursive_sorted(self.g.edges()),
                             [['a', 'b'], ['b', 'c']])

    def test_class_init_with_data_nonstring(self):
        self.g = MarkovModel([(1, 2), (2, 3)])

    def test_add_node_string(self):
        self.graph.add_node('a')
        self.assertListEqual(self.graph.nodes(), ['a'])

    def test_add_node_nonstring(self):
        self.graph.add_node(1)

    def test_add_nodes_from_string(self):
        self.graph.add_nodes_from(['a', 'b', 'c', 'd'])
        self.assertListEqual(sorted(self.graph.nodes()), ['a', 'b', 'c', 'd'])

    def test_add_nodes_from_non_string(self):
        self.graph.add_nodes_from([1, 2, 3, 4])

    def test_add_edge_string(self):
        self.graph.add_edge('d', 'e')
        self.assertListEqual(sorted(self.graph.nodes()), ['d', 'e'])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['d', 'e']])
        self.graph.add_nodes_from(['a', 'b', 'c'])
        self.graph.add_edge('a', 'b')
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['d', 'e']])

    def test_add_edge_nonstring(self):
        self.graph.add_edge(1, 2)

    def test_add_edge_selfloop(self):
        self.assertRaises(ValueError, self.graph.add_edge, 'a', 'a')

    def test_add_edges_from_string(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c')])
        self.assertListEqual(sorted(self.graph.nodes()), ['a', 'b', 'c'])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['b', 'c']])
        self.graph.add_nodes_from(['d', 'e', 'f'])
        self.graph.add_edges_from([('d', 'e'), ('e', 'f')])
        self.assertListEqual(sorted(self.graph.nodes()),
                             ['a', 'b', 'c', 'd', 'e', 'f'])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             hf.recursive_sorted([('a', 'b'), ('b', 'c'),
                                                  ('d', 'e'), ('e', 'f')]))

    def test_add_edges_from_nonstring(self):
        self.graph.add_edges_from([(1, 2), (2, 3)])

    def test_add_edges_from_self_loop(self):
        self.assertRaises(ValueError, self.graph.add_edges_from,
                          [('a', 'a')])

    def test_number_of_neighbors(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c')])
        self.assertEqual(len(self.graph.neighbors('b')), 2)

    def tearDown(self):
        del self.graph
Ejemplo n.º 4
0
class TestMarkovModelCreation(unittest.TestCase):
    def setUp(self):
        self.graph = MarkovModel()

    def test_class_init_without_data(self):
        self.assertIsInstance(self.graph, MarkovModel)

    def test_class_init_with_data_string(self):
        self.g = MarkovModel([('a', 'b'), ('b', 'c')])
        self.assertListEqual(sorted(self.g.nodes()), ['a', 'b', 'c'])
        self.assertListEqual(hf.recursive_sorted(self.g.edges()),
                             [['a', 'b'], ['b', 'c']])

    def test_class_init_with_data_nonstring(self):
        self.g = MarkovModel([(1, 2), (2, 3)])

    def test_add_node_string(self):
        self.graph.add_node('a')
        self.assertListEqual(self.graph.nodes(), ['a'])

    def test_add_node_nonstring(self):
        self.graph.add_node(1)

    def test_add_nodes_from_string(self):
        self.graph.add_nodes_from(['a', 'b', 'c', 'd'])
        self.assertListEqual(sorted(self.graph.nodes()), ['a', 'b', 'c', 'd'])

    def test_add_nodes_from_non_string(self):
        self.graph.add_nodes_from([1, 2, 3, 4])

    def test_add_edge_string(self):
        self.graph.add_edge('d', 'e')
        self.assertListEqual(sorted(self.graph.nodes()), ['d', 'e'])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['d', 'e']])
        self.graph.add_nodes_from(['a', 'b', 'c'])
        self.graph.add_edge('a', 'b')
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['d', 'e']])

    def test_add_edge_nonstring(self):
        self.graph.add_edge(1, 2)

    def test_add_edge_selfloop(self):
        self.assertRaises(ValueError, self.graph.add_edge, 'a', 'a')

    def test_add_edges_from_string(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c')])
        self.assertListEqual(sorted(self.graph.nodes()), ['a', 'b', 'c'])
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['b', 'c']])
        self.graph.add_nodes_from(['d', 'e', 'f'])
        self.graph.add_edges_from([('d', 'e'), ('e', 'f')])
        self.assertListEqual(sorted(self.graph.nodes()),
                             ['a', 'b', 'c', 'd', 'e', 'f'])
        self.assertListEqual(
            hf.recursive_sorted(self.graph.edges()),
            hf.recursive_sorted([('a', 'b'), ('b', 'c'), ('d', 'e'),
                                 ('e', 'f')]))

    def test_add_edges_from_nonstring(self):
        self.graph.add_edges_from([(1, 2), (2, 3)])

    def test_add_edges_from_self_loop(self):
        self.assertRaises(ValueError, self.graph.add_edges_from, [('a', 'a')])

    def test_number_of_neighbors(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c')])
        self.assertEqual(len(self.graph.neighbors('b')), 2)

    def tearDown(self):
        del self.graph
class TestUndirectedGraphTriangulation(unittest.TestCase):
    def setUp(self):
        self.graph = MarkovModel()

    def test_check_clique(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'a')])
        self.assertTrue(self.graph.is_clique(['a', 'b', 'c']))

    def test_is_triangulated(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'a')])
        self.assertTrue(self.graph.is_triangulated())

    def test_triangulation_h1_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H1', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'c'], ['a', 'd'],
                              ['b', 'c'], ['c', 'd']])

    def test_triangulation_h2_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H2', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'c'], ['a', 'd'],
                              ['b', 'c'], ['c', 'd']])

    def test_triangulation_h3_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H3', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h4_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H4', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h5_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H4', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h6_inplace(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        self.graph.triangulate(heuristic='H4', inplace=True)
        self.assertTrue(self.graph.is_triangulated())
        self.assertListEqual(hf.recursive_sorted(self.graph.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_cardinality_mismatch_raises_error(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        factor_list = [DiscreteFactor(edge, [2, 2], np.random.rand(4)) for edge in
                       self.graph.edges()]
        self.graph.add_factors(*factor_list)
        self.graph.add_factors(DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6)))
        self.assertRaises(ValueError, self.graph.triangulate)

    def test_triangulation_h1_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H1', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'c'], ['a', 'd'],
                              ['b', 'c'], ['c', 'd']])

    def test_triangulation_h2_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H2', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'c'], ['a', 'd'],
                              ['b', 'c'], ['c', 'd']])

    def test_triangulation_h3_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H3', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h4_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H4', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h5_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H5', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_triangulation_h6_create_new(self):
        self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'd'),
                                   ('d', 'a')])
        phi1 = DiscreteFactor(['a', 'b'], [2, 3], np.random.rand(6))
        phi2 = DiscreteFactor(['b', 'c'], [3, 4], np.random.rand(12))
        phi3 = DiscreteFactor(['c', 'd'], [4, 5], np.random.rand(20))
        phi4 = DiscreteFactor(['d', 'a'], [5, 2], np.random.random(10))
        self.graph.add_factors(phi1, phi2, phi3, phi4)
        H = self.graph.triangulate(heuristic='H6', inplace=True)
        self.assertListEqual(hf.recursive_sorted(H.edges()),
                             [['a', 'b'], ['a', 'd'], ['b', 'c'],
                              ['b', 'd'], ['c', 'd']])

    def test_copy(self):
        # Setup the original graph
        self.graph.add_nodes_from(['a', 'b'])
        self.graph.add_edges_from([('a', 'b')])

        # Generate the copy
        copy = self.graph.copy()

        # Ensure the copied model is correct
        self.assertTrue(copy.check_model())

        # Basic sanity checks to ensure the graph was copied correctly
        self.assertEqual(len(copy.nodes()), 2)
        self.assertListEqual(copy.neighbors('a'), ['b'])
        self.assertListEqual(copy.neighbors('b'), ['a'])

        # Modify the original graph ...
        self.graph.add_nodes_from(['c'])
        self.graph.add_edges_from([('c', 'b')])

        # ... and ensure none of those changes get propagated
        self.assertEqual(len(copy.nodes()), 2)
        self.assertListEqual(copy.neighbors('a'), ['b'])
        self.assertListEqual(copy.neighbors('b'), ['a'])
        with self.assertRaises(nx.NetworkXError):
            copy.neighbors('c')

        # Ensure the copy has no factors at this point
        self.assertEqual(len(copy.get_factors()), 0)

        # Add factors to the original graph
        phi1 = DiscreteFactor(['a', 'b'], [2, 2], [[0.3, 0.7], [0.9, 0.1]])
        self.graph.add_factors(phi1)

        # The factors should not get copied over
        with self.assertRaises(AssertionError):
            self.assertListEqual(copy.get_factors(), self.graph.get_factors())

        # Create a fresh copy
        del copy
        copy = self.graph.copy()
        self.assertListEqual(copy.get_factors(), self.graph.get_factors())

        # If we change factors in the original, it should not be passed to the clone
        phi1.values = np.array([[0.5, 0.5], [0.5, 0.5]])
        self.assertNotEqual(self.graph.get_factors(), copy.get_factors())

        # Start with a fresh copy
        del copy
        self.graph.add_nodes_from(['d'])
        copy = self.graph.copy()

        # Ensure an unconnected node gets copied over as well
        self.assertEqual(len(copy.nodes()), 4)
        self.assertListEqual(self.graph.neighbors('a'), ['b'])
        self.assertTrue('a' in self.graph.neighbors('b'))
        self.assertTrue('c' in self.graph.neighbors('b'))
        self.assertListEqual(self.graph.neighbors('c'), ['b'])
        self.assertListEqual(self.graph.neighbors('d'), [])

        # Verify that changing the copied model should not update the original
        copy.add_nodes_from(['e'])
        self.assertListEqual(copy.neighbors('e'), [])
        with self.assertRaises(nx.NetworkXError):
            self.graph.neighbors('e')

        # Verify that changing edges in the copy doesn't create edges in the original
        copy.add_edges_from([('d', 'b')])

        self.assertTrue('a' in copy.neighbors('b'))
        self.assertTrue('c' in copy.neighbors('b'))
        self.assertTrue('d' in copy.neighbors('b'))

        self.assertTrue('a' in self.graph.neighbors('b'))
        self.assertTrue('c' in self.graph.neighbors('b'))
        self.assertFalse('d' in self.graph.neighbors('b'))

        # If we remove factors from the copied model, it should not reflect in the original
        copy.remove_factors(phi1)
        self.assertEqual(len(self.graph.get_factors()), 1)
        self.assertEqual(len(copy.get_factors()), 0)

    def tearDown(self):
        del self.graph