Repository: pgmpy/pgmpy
Pull request #713 (2016-08-01 11:00:15)

Fix/xml bif transposed cpd
Fixes issue #712. This was a bug not caught by existing tests as they didn't have coverage of get_model(). I've added a test and updated code and test code for XMLBIFReader and XMLBIFWriter. 


Patch:
diff --git a/pgmpy/readwrite/XMLBIF.py b/pgmpy/readwrite/XMLBIF.py
index 69461336..e4212c7a 100644
--- a/pgmpy/readwrite/XMLBIF.py
+++ b/pgmpy/readwrite/XMLBIF.py
@@ -1,5 +1,13 @@
 #!/usr/bin/env python
 
+
+from io import BytesIO
+import pyparsing as pp
+
+
+# TODO input and output state
+
+
 try:
     from lxml import etree
 except ImportError:
@@ -15,13 +23,14 @@ import numpy as np
 
 from pgmpy.models import BayesianModel
 from pgmpy.factors import TabularCPD, State
-from pgmpy.extern.six.moves import map, range
+from pgmpy.extern.six.moves import map
 
 
 class XMLBIFReader(object):
     """
     Base class for reading network file in XMLBIF format.
     """
+
     def __init__(self, path=None, string=None):
         """
         Initialisation of XMLBIFReader object.
@@ -115,7 +124,7 @@ class XMLBIFReader(object):
          'hear-bark': ['dog-out'],
          'light-on': ['family-out']}
         """
-        variable_parents = {definition.find('FOR').text: [edge.text for edge in definition.findall('GIVEN')][::-1]
+        variable_parents = {definition.find('FOR').text: [edge.text for edge in definition.findall('GIVEN')]
                             for definition in self.network.findall('DEFINITION')}
         return variable_parents
 
@@ -144,7 +153,7 @@ class XMLBIFReader(object):
         for variable in variable_CPD:
             arr = np.array(variable_CPD[variable])
             arr = arr.reshape((len(self.variable_states[variable]),
-                               arr.size//len(self.variable_states[variable])))
+                               arr.size // len(self.variable_states[variable])), order='F')
             variable_CPD[variable] = arr
         return variable_CPD
 
@@ -172,18 +181,19 @@ class XMLBIFReader(object):
 
         tabular_cpds = []
         for var, values in self.variable_CPD.items():
+            evidence_card = [len(self.variable_states[evidence_var]) for evidence_var in self.variable_parents[var]]
             cpd = TabularCPD(var, len(self.variable_states[var]), values,
                              evidence=self.variable_parents[var],
-                             evidence_card=[len(self.variable_states[evidence_var])
-                                            for evidence_var in self.variable_parents[var]])
+                             evidence_card=evidence_card, state_names=self.get_states())
             tabular_cpds.append(cpd)
 
         model.add_cpds(*tabular_cpds)
 
         for node, properties in self.variable_property.items():
             for prop in properties:
-                prop_name, prop_value = map(lambda t: t.strip(), prop.split('='))
-                model.node[node][prop_name] = prop_value
+                if prop is not None:
+                    prop_name, prop_value = map(lambda t: t.strip(), prop.split('='))
+                    model.node[node][prop_name] = prop_value
 
         return model
 
@@ -192,6 +202,7 @@ class XMLBIFWriter(object):
     """
     Base class for writing XMLBIF network file format.
     """
+
     def __init__(self, model, encoding='utf-8', prettyprint=True):
         """
         Initialise a XMLBIFWriter object.
@@ -216,10 +227,12 @@ class XMLBIFWriter(object):
         self.encoding = encoding
         self.prettyprint = prettyprint
 
-        self.xml = etree.Element("BIF", attrib={'version': '0.3'})
+        self.xml = etree.Element("BIF", attrib={'VERSION': '0.3'})
         self.network = etree.SubElement(self.xml, 'NETWORK')
         if self.model.name:
             etree.SubElement(self.network, 'NAME').text = self.model.name
+        else:
+            etree.SubElement(self.network, 'NAME').text = "UNTITLED"
 
         self.variables = self.get_variables()
         self.states = self.get_states()
@@ -233,20 +246,23 @@ class XMLBIFWriter(object):
         """
         if self.prettyprint:
             self.indent(self.xml)
-        return etree.tostring(self.xml, encoding=self.encoding)
+        f = BytesIO()
+        et = etree.ElementTree(self.xml)
+        et.write(f, encoding=self.encoding, xml_declaration=True)
+        return f.getvalue().decode(self.encoding)
 
     def indent(self, elem, level=0):
         """
         Inplace prettyprint formatter.
         """
-        i = "\n" + level*"  "
+        i = "\n" + level * "  "
         if len(elem):
             if not elem.text or not elem.text.strip():
                 elem.text = i + "  "
             if not elem.tail or not elem.tail.strip():
                 elem.tail = i
             for elem in elem:
-                self.indent(elem, level+1)
+                self.indent(elem, level + 1)
             if not elem.tail or not elem.tail.strip():
                 elem.tail = i
         else:
@@ -301,12 +317,28 @@ class XMLBIFWriter(object):
         for cpd in cpds:
             var = cpd.variable
             outcome_tag[var] = []
-            for state in [State(var, state) for state in range(cpd.get_cardinality([var])[var])]:
+            if cpd.state_names is None or cpd.state_names.get(var) is None:
+                states = range(cpd.get_cardinality([var])[var])
+            else:
+                states = cpd.state_names[var]
+
+            for state in states:
                 state_tag = etree.SubElement(self.variables[var], "OUTCOME")
-                state_tag.text = str(state.state)
+                state_tag.text = self._make_valid_state_name(state)
                 outcome_tag[var].append(state_tag)
         return outcome_tag
 
+    def _make_valid_state_name(self, state_name):
+        """Transform the input state_name into a valid state in XMLBIF.
+        XMLBIF states must start with a letter an only contain letters,
+        numbers and underscores.
+        """
+        s = str(state_name)
+        s_fixed = pp.CharsNotIn(pp.alphanums + "_").setParseAction(pp.replaceWith("_")).transformString(s)
+        if not s_fixed[0].isalpha():
+            s_fixed = "state" + s_fixed
+        return s_fixed
+
     def get_properties(self):
         """
         Add property to variables in XMLBIF
@@ -387,7 +419,7 @@ class XMLBIFWriter(object):
         for cpd in cpds:
             table_tag[cpd.variable] = etree.SubElement(definition_tag[cpd.variable], "TABLE")
             table_tag[cpd.variable].text = ''
-            for val in cpd.values.ravel():
+            for val in cpd.get_cpd().ravel(order="F"):
                 table_tag[cpd.variable].text += str(val) + ' '
 
         return table_tag
@@ -405,6 +437,5 @@ class XMLBIFWriter(object):
         >>> writer = XMLBIFWriter(model)
         >>> writer.write_xmlbif(test_file)
         """
-        writer = self.__str__()[:-1].decode('utf-8')
         with open(filename, 'w') as fout:
-            fout.write(writer)
+            fout.write(self.__str__())


Test patch:
diff --git a/pgmpy/tests/test_readwrite/test_XMLBIF.py b/pgmpy/tests/test_readwrite/test_XMLBIF.py
index d5d13ae7..e3f13ee5 100644
--- a/pgmpy/tests/test_readwrite/test_XMLBIF.py
+++ b/pgmpy/tests/test_readwrite/test_XMLBIF.py
@@ -1,14 +1,13 @@
 import os
 import unittest
 import warnings
-
 import numpy as np
 import numpy.testing as np_test
 
 from pgmpy.readwrite import XMLBIFReader, XMLBIFWriter
 from pgmpy.models import BayesianModel
 from pgmpy.factors import TabularCPD
-from pgmpy.extern.six.moves import map
+
 
 try:
     from lxml import etree
@@ -21,409 +20,321 @@ except ImportError:
         except ImportError:
             warnings.warn("Failed to import ElementTree from any known place")
 
+TEST_FILE = """<?xml version="1.0" encoding="US-ASCII"?>
+
+
+<!--
+    Bayesian network in XMLBIF v0.3 (BayesNet Interchange Format)
+    Produced by JavaBayes (http://www.cs.cmu.edu/~javabayes/
+    Output created Mon Aug 01 10:33:28 AEST 2016
+-->
+
+
+
+<!-- DTD for the XMLBIF 0.3 format -->
+<!DOCTYPE BIF [
+    <!ELEMENT BIF ( NETWORK )*>
+          <!ATTLIST BIF VERSION CDATA #REQUIRED>
+    <!ELEMENT NETWORK ( NAME, ( PROPERTY | VARIABLE | DEFINITION )* )>
+    <!ELEMENT NAME (#PCDATA)>
+    <!ELEMENT VARIABLE ( NAME, ( OUTCOME |  PROPERTY )* ) >
+          <!ATTLIST VARIABLE TYPE (nature|decision|utility) "nature">
+    <!ELEMENT OUTCOME (#PCDATA)>
+    <!ELEMENT DEFINITION ( FOR | GIVEN | TABLE | PROPERTY )* >
+    <!ELEMENT FOR (#PCDATA)>
+    <!ELEMENT GIVEN (#PCDATA)>
+    <!ELEMENT TABLE (#PCDATA)>
+    <!ELEMENT PROPERTY (#PCDATA)>
+]>
+
+
+<BIF VERSION="0.3">
+<NETWORK>
+<NAME>Dog_Problem</NAME>
+
+<!-- Variables -->
+<VARIABLE TYPE="nature">
+    <NAME>light_on</NAME>
+    <OUTCOME>true</OUTCOME>
+    <OUTCOME>false</OUTCOME>
+    <PROPERTY>position = (73, 165)</PROPERTY>
+</VARIABLE>
+
+<VARIABLE TYPE="nature">
+    <NAME>bowel_problem</NAME>
+    <OUTCOME>true</OUTCOME>
+    <OUTCOME>false</OUTCOME>
+    <PROPERTY>position = (190, 69)</PROPERTY>
+</VARIABLE>
+
+<VARIABLE TYPE="nature">
+    <NAME>dog_out</NAME>
+    <OUTCOME>true</OUTCOME>
+    <OUTCOME>false</OUTCOME>
+    <PROPERTY>position = (155, 165)</PROPERTY>
+</VARIABLE>
+
+<VARIABLE TYPE="nature">
+    <NAME>hear_bark</NAME>
+    <OUTCOME>true</OUTCOME>
+    <OUTCOME>false</OUTCOME>
+    <PROPERTY>position = (154, 241)</PROPERTY>
+</VARIABLE>
+
+<VARIABLE TYPE="nature">
+    <NAME>family_out</NAME>
+    <OUTCOME>true</OUTCOME>
+    <OUTCOME>false</OUTCOME>
+    <PROPERTY>position = (112, 69)</PROPERTY>
+</VARIABLE>
+
+
+<!-- Probability distributions -->
+<DEFINITION>
+    <FOR>light_on</FOR>
+    <GIVEN>family_out</GIVEN>
+    <TABLE>0.6 0.4 0.05 0.95 </TABLE>
+</DEFINITION>
+
+<DEFINITION>
+    <FOR>bowel_problem</FOR>
+    <TABLE>0.01 0.99 </TABLE>
+</DEFINITION>
+
+<DEFINITION>
+    <FOR>dog_out</FOR>
+    <GIVEN>bowel_problem</GIVEN>
+    <GIVEN>family_out</GIVEN>
+    <TABLE>0.99 0.01 0.97 0.03 0.9 0.1 0.3 0.7 </TABLE>
+</DEFINITION>
+
+<DEFINITION>
+    <FOR>hear_bark</FOR>
+    <GIVEN>dog_out</GIVEN>
+    <TABLE>0.7 0.3 0.01 0.99 </TABLE>
+</DEFINITION>
+
+<DEFINITION>
+    <FOR>family_out</FOR>
+    <TABLE>0.15 0.85 </TABLE>
+</DEFINITION>
+
+
+</NETWORK>
+</BIF>"""
+
+
+class TestXMLBIFReaderMethods(unittest.TestCase):
 
-class TestXMLBIFReaderMethodsString(unittest.TestCase):
     def setUp(self):
-        self.reader = XMLBIFReader(string="""
-        <BIF VERSION="0.3">
-        <NETWORK>
-        <NAME>Dog-Problem</NAME>
-
-        <VARIABLE TYPE="nature">
-            <NAME>light-on</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (73, 165)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>bowel-problem</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (190, 69)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>dog-out</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (155, 165)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>hear-bark</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (154, 241)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>family-out</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (112, 69)</PROPERTY>
-        </VARIABLE>
-
-
-        <DEFINITION>
-            <FOR>light-on</FOR>
-            <GIVEN>family-out</GIVEN>
-            <TABLE>0.6 0.4 0.05 0.95 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>bowel-problem</FOR>
-            <TABLE>0.01 0.99 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>dog-out</FOR>
-            <GIVEN>bowel-problem</GIVEN>
-            <GIVEN>family-out</GIVEN>
-            <TABLE>0.99 0.01 0.97 0.03 0.9 0.1 0.3  0.7 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>hear-bark</FOR>
-            <GIVEN>dog-out</GIVEN>
-            <TABLE>0.7 0.3 0.01 0.99 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>family-out</FOR>
-            <TABLE>0.15 0.85 </TABLE>
-        </DEFINITION>
-
-
-        </NETWORK>
-        </BIF>
-        """)
+        self.reader = XMLBIFReader(string=TEST_FILE)
 
     def test_get_variables(self):
-        var_expected = ['light-on', 'bowel-problem', 'dog-out',
-                        'hear-bark', 'family-out']
+        var_expected = ['light_on', 'bowel_problem', 'dog_out',
+                        'hear_bark', 'family_out']
         self.assertListEqual(self.reader.variables, var_expected)
 
     def test_get_states(self):
-        states_expected = {'bowel-problem': ['true', 'false'],
-                           'dog-out': ['true', 'false'],
-                           'family-out': ['true', 'false'],
-                           'hear-bark': ['true', 'false'],
-                           'light-on': ['true', 'false']}
+        states_expected = {'bowel_problem': ['true', 'false'],
+                           'dog_out': ['true', 'false'],
+                           'family_out': ['true', 'false'],
+                           'hear_bark': ['true', 'false'],
+                           'light_on': ['true', 'false']}
         states = self.reader.variable_states
         for variable in states_expected:
             self.assertListEqual(states_expected[variable],
                                  states[variable])
 
     def test_get_parents(self):
-        parents_expected = {'bowel-problem': [],
-                            'dog-out': ['family-out', 'bowel-problem'],
-                            'family-out': [],
-                            'hear-bark': ['dog-out'],
-                            'light-on': ['family-out']}
+        parents_expected = {'bowel_problem': [],
+                            'dog_out': ['bowel_problem', 'family_out'],
+                            'family_out': [],
+                            'hear_bark': ['dog_out'],
+                            'light_on': ['family_out']}
         parents = self.reader.variable_parents
         for variable in parents_expected:
             self.assertListEqual(parents_expected[variable],
                                  parents[variable])
 
     def test_get_edges(self):
-        edges_expected = [['family-out', 'dog-out'],
-                          ['bowel-problem', 'dog-out'],
-                          ['family-out', 'light-on'],
-                          ['dog-out', 'hear-bark']]
+        edges_expected = [['family_out', 'dog_out'],
+                          ['bowel_problem', 'dog_out'],
+                          ['family_out', 'light_on'],
+                          ['dog_out', 'hear_bark']]
         self.assertListEqual(sorted(self.reader.edge_list),
                              sorted(edges_expected))
 
     def test_get_cpd(self):
-        cpd_expected = {'bowel-problem': np.array([[0.01],
+        cpd_expected = {'bowel_problem': np.array([[0.01],
                                                    [0.99]]),
-                        'dog-out': np.array([[0.99, 0.01, 0.97, 0.03],
-                                             [0.9, 0.1, 0.3, 0.7]]),
-                        'family-out': np.array([[0.15],
+                        'dog_out': np.array([[0.99, 0.97, 0.9, 0.3],
+                                             [0.01, 0.03, 0.1, 0.7]]),
+                        'family_out': np.array([[0.15],
                                                 [0.85]]),
-                        'hear-bark': np.array([[0.7, 0.3],
-                                               [0.01, 0.99]]),
-                        'light-on': np.array([[0.6, 0.4],
-                                              [0.05, 0.95]])}
+                        'hear_bark': np.array([[0.7, 0.01],
+                                               [0.3, 0.99]]),
+                        'light_on': np.array([[0.6, 0.05],
+                                              [0.4, 0.95]])}
         cpd = self.reader.variable_CPD
         for variable in cpd_expected:
             np_test.assert_array_equal(cpd_expected[variable],
                                        cpd[variable])
 
     def test_get_property(self):
-        property_expected = {'bowel-problem': ['position = (190, 69)'],
-                             'dog-out': ['position = (155, 165)'],
-                             'family-out': ['position = (112, 69)'],
-                             'hear-bark': ['position = (154, 241)'],
-                             'light-on': ['position = (73, 165)']}
+        property_expected = {'bowel_problem': ['position = (190, 69)'],
+                             'dog_out': ['position = (155, 165)'],
+                             'family_out': ['position = (112, 69)'],
+                             'hear_bark': ['position = (154, 241)'],
+                             'light_on': ['position = (73, 165)']}
         prop = self.reader.variable_property
         for variable in property_expected:
             self.assertListEqual(property_expected[variable],
                                  prop[variable])
 
+    def test_model(self):
+        self.reader.get_model().check_model()
+
     def tearDown(self):
         del self.reader
 
 
 class TestXMLBIFReaderMethodsFile(unittest.TestCase):
+
     def setUp(self):
-        xml = """
-        <BIF VERSION="0.3">
-        <NETWORK>
-        <NAME>Dog-Problem</NAME>
-
-        <VARIABLE TYPE="nature">
-            <NAME>light-on</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (73, 165)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>bowel-problem</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (190, 69)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>dog-out</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (155, 165)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>hear-bark</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (154, 241)</PROPERTY>
-        </VARIABLE>
-
-        <VARIABLE TYPE="nature">
-            <NAME>family-out</NAME>
-            <OUTCOME>true</OUTCOME>
-            <OUTCOME>false</OUTCOME>
-            <PROPERTY>position = (112, 69)</PROPERTY>
-        </VARIABLE>
-
-
-        <DEFINITION>
-            <FOR>light-on</FOR>
-            <GIVEN>family-out</GIVEN>
-            <TABLE>0.6 0.4 0.05 0.95 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>bowel-problem</FOR>
-            <TABLE>0.01 0.99 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>dog-out</FOR>
-            <GIVEN>bowel-problem</GIVEN>
-            <GIVEN>family-out</GIVEN>
-            <TABLE>0.99 0.01 0.97 0.03 0.9 0.1 0.3  0.7 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>hear-bark</FOR>
-            <GIVEN>dog-out</GIVEN>
-            <TABLE>0.7 0.3 0.01 0.99 </TABLE>
-        </DEFINITION>
-
-        <DEFINITION>
-            <FOR>family-out</FOR>
-            <TABLE>0.15 0.85 </TABLE>
-        </DEFINITION>
-
-
-        </NETWORK>
-        </BIF>
-        """
-        with open("test_bif.xml", 'w') as fout:
-            fout.write(xml)
-        self.reader = XMLBIFReader("test_bif.xml")
+        with open("dog_problem.xml", 'w') as fout:
+            fout.write(TEST_FILE)
+        self.reader = XMLBIFReader("dog_problem.xml")
 
     def test_get_variables(self):
-        var_expected = ['light-on', 'bowel-problem', 'dog-out',
-                        'hear-bark', 'family-out']
+        var_expected = ['light_on', 'bowel_problem', 'dog_out',
+                        'hear_bark', 'family_out']
         self.assertListEqual(self.reader.variables, var_expected)
 
     def test_get_states(self):
-        states_expected = {'bowel-problem': ['true', 'false'],
-                           'dog-out': ['true', 'false'],
-                           'family-out': ['true', 'false'],
-                           'hear-bark': ['true', 'false'],
-                           'light-on': ['true', 'false']}
+        states_expected = {'bowel_problem': ['true', 'false'],
+                           'dog_out': ['true', 'false'],
+                           'family_out': ['true', 'false'],
+                           'hear_bark': ['true', 'false'],
+                           'light_on': ['true', 'false']}
         states = self.reader.variable_states
         for variable in states_expected:
             self.assertListEqual(states_expected[variable],
                                  states[variable])
 
     def test_get_parents(self):
-        parents_expected = {'bowel-problem': [],
-                            'dog-out': ['family-out', 'bowel-problem'],
-                            'family-out': [],
-                            'hear-bark': ['dog-out'],
-                            'light-on': ['family-out']}
+        parents_expected = {'bowel_problem': [],
+                            'dog_out': ['bowel_problem', 'family_out'],
+                            'family_out': [],
+                            'hear_bark': ['dog_out'],
+                            'light_on': ['family_out']}
         parents = self.reader.variable_parents
         for variable in parents_expected:
             self.assertListEqual(parents_expected[variable],
                                  parents[variable])
 
     def test_get_edges(self):
-        edges_expected = [['family-out', 'dog-out'],
-                          ['bowel-problem', 'dog-out'],
-                          ['family-out', 'light-on'],
-                          ['dog-out', 'hear-bark']]
+        edges_expected = [['family_out', 'dog_out'],
+                          ['bowel_problem', 'dog_out'],
+                          ['family_out', 'light_on'],
+                          ['dog_out', 'hear_bark']]
         self.assertListEqual(sorted(self.reader.edge_list),
                              sorted(edges_expected))
 
     def test_get_cpd(self):
-        cpd_expected = {'bowel-problem': np.array([[0.01],
+        cpd_expected = {'bowel_problem': np.array([[0.01],
                                                    [0.99]]),
-                        'dog-out': np.array([[0.99, 0.01, 0.97, 0.03],
-                                             [0.9, 0.1, 0.3, 0.7]]),
-                        'family-out': np.array([[0.15],
+                        'dog_out': np.array([[0.99, 0.97, 0.9, 0.3],
+                                             [0.01, 0.03, 0.1, 0.7]]),
+                        'family_out': np.array([[0.15],
                                                 [0.85]]),
-                        'hear-bark': np.array([[0.7, 0.3],
-                                               [0.01, 0.99]]),
-                        'light-on': np.array([[0.6, 0.4],
-                                              [0.05, 0.95]])}
+                        'hear_bark': np.array([[0.7, 0.01],
+                                               [0.3, 0.99]]),
+                        'light_on': np.array([[0.6, 0.05],
+                                              [0.4, 0.95]])}
         cpd = self.reader.variable_CPD
         for variable in cpd_expected:
             np_test.assert_array_equal(cpd_expected[variable],
                                        cpd[variable])
 
     def test_get_property(self):
-        property_expected = {'bowel-problem': ['position = (190, 69)'],
-                             'dog-out': ['position = (155, 165)'],
-                             'family-out': ['position = (112, 69)'],
-                             'hear-bark': ['position = (154, 241)'],
-                             'light-on': ['position = (73, 165)']}
+        property_expected = {'bowel_problem': ['position = (190, 69)'],
+                             'dog_out': ['position = (155, 165)'],
+                             'family_out': ['position = (112, 69)'],
+                             'hear_bark': ['position = (154, 241)'],
+                             'light_on': ['position = (73, 165)']}
         prop = self.reader.variable_property
         for variable in property_expected:
             self.assertListEqual(property_expected[variable],
                                  prop[variable])
 
+    def test_model(self):
+        self.reader.get_model().check_model()
+
     def tearDown(self):
         del self.reader
-        os.remove("test_bif.xml")
+        os.remove("dog_problem.xml")
 
 
 class TestXMLBIFWriterMethodsString(unittest.TestCase):
+
     def setUp(self):
-        edges = [['family-out', 'dog-out'],
-                 ['bowel-problem', 'dog-out'],
-                 ['family-out', 'light-on'],
-                 ['dog-out', 'hear-bark']]
-        cpds = {'bowel-problem': np.array([[0.01],
-                                           [0.99]]),
-                'dog-out': np.array([[0.99, 0.01, 0.97, 0.03],
-                                     [0.9, 0.1, 0.3, 0.7]]),
-                'family-out': np.array([[0.15],
-                                        [0.85]]),
-                'hear-bark': np.array([[0.7, 0.3],
-                                       [0.01, 0.99]]),
-                'light-on': np.array([[0.6, 0.4],
-                                      [0.05, 0.95]])}
-        states = {'bowel-problem': ['true', 'false'],
-                  'dog-out': ['true', 'false'],
-                  'family-out': ['true', 'false'],
-                  'hear-bark': ['true', 'false'],
-                  'light-on': ['true', 'false']}
-        parents = {'bowel-problem': [],
-                   'dog-out': ['family-out', 'bowel-problem'],
-                   'family-out': [],
-                   'hear-bark': ['dog-out'],
-                   'light-on': ['family-out']}
-        properties = {'bowel-problem': ['position = (190, 69)'],
-                      'dog-out': ['position = (155, 165)'],
-                      'family-out': ['position = (112, 69)'],
-                      'hear-bark': ['position = (154, 241)'],
-                      'light-on': ['position = (73, 165)']}
-
-        self.model = BayesianModel(edges)
-
-        tabular_cpds = []
-        for var, values in cpds.items():
-            cpd = TabularCPD(var, len(states[var]), values,
-                             evidence=parents[var],
-                             evidence_card=[len(states[evidence_var])
-                                            for evidence_var in parents[var]])
-            tabular_cpds.append(cpd)
-        self.model.add_cpds(*tabular_cpds)
-
-        for node, properties in properties.items():
-            for prop in properties:
-                prop_name, prop_value = map(lambda t: t.strip(), prop.split('='))
-                self.model.node[node][prop_name] = prop_value
-
-        self.writer = XMLBIFWriter(model=self.model)
-
-    def test_file(self):
-        self.expected_xml = etree.XML("""<BIF version="0.3">
-  <NETWORK>
-    <VARIABLE TYPE="nature">
-      <NAME>bowel-problem</NAME>
-      <OUTCOME>0</OUTCOME>
-      <OUTCOME>1</OUTCOME>
-      <PROPERTY>position = (190, 69)</PROPERTY>
-    </VARIABLE>
-    <VARIABLE TYPE="nature">
-      <NAME>dog-out</NAME>
-      <OUTCOME>0</OUTCOME>
-      <OUTCOME>1</OUTCOME>
-      <PROPERTY>position = (155, 165)</PROPERTY>
-    </VARIABLE>
-    <VARIABLE TYPE="nature">
-      <NAME>family-out</NAME>
-      <OUTCOME>0</OUTCOME>
-      <OUTCOME>1</OUTCOME>
-      <PROPERTY>position = (112, 69)</PROPERTY>
-    </VARIABLE>
-    <VARIABLE TYPE="nature">
-      <NAME>hear-bark</NAME>
-      <OUTCOME>0</OUTCOME>
-      <OUTCOME>1</OUTCOME>
-      <PROPERTY>position = (154, 241)</PROPERTY>
-    </VARIABLE>
-    <VARIABLE TYPE="nature">
-      <NAME>light-on</NAME>
-      <OUTCOME>0</OUTCOME>
-      <OUTCOME>1</OUTCOME>
-      <PROPERTY>position = (73, 165)</PROPERTY>
-    </VARIABLE>
-    <DEFINITION>
-      <FOR>bowel-problem</FOR>
-      <TABLE>0.01 0.99 </TABLE>
-    </DEFINITION>
-    <DEFINITION>
-      <FOR>dog-out</FOR>
-      <GIVEN>bowel-problem</GIVEN>
-      <GIVEN>family-out</GIVEN>
-      <TABLE>0.99 0.01 0.97 0.03 0.9 0.1 0.3 0.7 </TABLE>
-    </DEFINITION>
-    <DEFINITION>
-      <FOR>family-out</FOR>
-      <TABLE>0.15 0.85 </TABLE>
-    </DEFINITION>
-    <DEFINITION>
-      <FOR>hear-bark</FOR>
-      <GIVEN>dog-out</GIVEN>
-      <TABLE>0.7 0.3 0.01 0.99 </TABLE>
-    </DEFINITION>
-    <DEFINITION>
-      <FOR>light-on</FOR>
-      <GIVEN>family-out</GIVEN>
-      <TABLE>0.6 0.4 0.05 0.95 </TABLE>
-    </DEFINITION>
-  </NETWORK>
-</BIF>""")
-        self.maxDiff = None
-        self.writer.write_xmlbif("test_bif.xml")
-        with open("test_bif.xml", "r") as myfile:
-            data = myfile.read()
-        self.assertEqual(str(self.writer.__str__()[:-1]), str(etree.tostring(self.expected_xml)))
-        self.assertEqual(str(data), str(etree.tostring(self.expected_xml).decode('utf-8')))
+        reader = XMLBIFReader(string=TEST_FILE)
+        self.expected_model = reader.get_model()
+        self.writer = XMLBIFWriter(self.expected_model)
+
+        self.model_stateless = BayesianModel([('D', 'G'), ('I', 'G'), ('G', 'L'), ('I', 'S')])
+        self.cpd_d = TabularCPD(variable='D', variable_card=2, values=[[0.6, 0.4]])
+        self.cpd_i = TabularCPD(variable='I', variable_card=2, values=[[0.7, 0.3]])
+
+        self.cpd_g = TabularCPD(variable='G', variable_card=3,
+                                values=[[0.3, 0.05, 0.9,  0.5],
+                                        [0.4, 0.25, 0.08, 0.3],
+                                        [0.3, 0.7,  0.02, 0.2]],
+                                evidence=['I', 'D'],
+                                evidence_card=[2, 2])
+
+        self.cpd_l = TabularCPD(variable='L', variable_card=2,
+                                values=[[0.1, 0.4, 0.99],
+                                        [0.9, 0.6, 0.01]],
+                                evidence=['G'],
+                                evidence_card=[3])
+
+        self.cpd_s = TabularCPD(variable='S', variable_card=2,
+                                values=[[0.95, 0.2],
+                                        [0.05, 0.8]],
+                                evidence=['I'],
+                                evidence_card=[2])
+
+        self.model_stateless.add_cpds(self.cpd_d, self.cpd_i, self.cpd_g, self.cpd_l, self.cpd_s)
+        self.writer_stateless = XMLBIFWriter(self.model_stateless)
+
+    def test_write_xmlbif_statefull(self):
+        self.writer.write_xmlbif("dog_problem_output.xbif")
+        with open("dog_problem_output.xbif", "r") as f:
+            file_text = f.read()
+        reader = XMLBIFReader(string=file_text)
+        model = reader.get_model()
+        self.assert_models_equivelent(self.expected_model, model)
+        os.remove("dog_problem_output.xbif")
+
+    def test_write_xmlbif_stateless(self):
+        self.writer_stateless.write_xmlbif("grade_problem_output.xbif")
+        with open("grade_problem_output.xbif", 'r') as f:
+            reader = XMLBIFReader(f)
+        model = reader.get_model()
+        self.assert_models_equivelent(self.model_stateless, model)
+        self.assertDictEqual({'G': ['state0', 'state1', 'state2'],
+                              'I': ['state0', 'state1'],
+                              'D': ['state0', 'state1'],
+                              'S': ['state0', 'state1'],
+                              'L': ['state0', 'state1']},
+                             model.get_cpds('D').state_names)
+        os.remove("grade_problem_output.xbif")
+
+    def assert_models_equivelent(self, expected, got):
+        self.assertSetEqual(set(expected.nodes()), set(got.nodes()))
+        for node in expected.nodes():
+            self.assertListEqual(expected.get_parents(node), got.get_parents(node))
+            cpds_expected = expected.get_cpds(node=node)
+            cpds_got = got.get_cpds(node=node)
+            np_test.assert_array_equal(cpds_expected.values, cpds_got.values)