Repository: pgmpy/pgmpy
Pull request #758 (2016-10-02 00:05:53)

Refactor base
Changes:
1. Removes `**kwargs` argument from `add_node`, `add_edge`, `add_nodes_from` and `add_edges_from` methods.
2. Adds `weight` argument to above methods.
3. Improves docstrings and doctests
4. Rename `check_clique` method to `is_clique`.
5. Changes in test according to the above changes.

ankurankan: @khalibartan @yashu-seth Reviews ?
coveralls: 
[![Coverage Status](https://coveralls.io/builds/8169746/badge)](https://coveralls.io/builds/8169746)

Coverage decreased (-0.03%) to 96.074% when pulling **0165cd29ec04304a77ea8aed870cda5b99f36dea on ankurankan:maharefactor/base** into **d9de4a66b267e53421f095a32aca76b556d0fabc on pgmpy:dev**.

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

Coverage decreased (-0.03%) to 96.074% when pulling **0165cd29ec04304a77ea8aed870cda5b99f36dea on ankurankan:maharefactor/base** into **d9de4a66b267e53421f095a32aca76b556d0fabc on pgmpy:dev**.

khalibartan: @ankurankan what additional configuration parameters was kwargs allowing?
ankurankan: @khalibartan It was just according to the networkx API in which you can store any parameter for the node or the edge. But we were just using it to store weight in pgmpy. We can still store extra values by accessing the underlying dictionary but removing **kwargs just makes the API more clear.
khalibartan: @ankurankan Seems fine to me.

Patch:
diff --git a/examples/Extending pgmpy.ipynb b/examples/Extending pgmpy.ipynb
new file mode 100644
index 00000000..c3d856ab
--- /dev/null
+++ b/examples/Extending pgmpy.ipynb
@@ -0,0 +1,169 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "collapsed": true
+   },
+   "source": [
+    "It's really easy to extend pgmpy to quickly prototype your ideas. pgmpy has a base abstract class for most of main functionalities like: `BaseInference` for inference, `BaseFactor` for model parameters, `BaseEstimators` for parameter and model learning. For adding a new feature to pgmpy we just need to implement a new class inheriting one of these base classes and then we can use the new class with other functionality of pgmpy.\n",
+    "\n",
+    "In this example we will see how to write a new inference algorithm. We will take the example of a very simple algorithm in which we will multiply all the factors/CPD of the network and marginalize over variable to get the desired query."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "# A simple Exact inference algorithm\n",
+    "\n",
+    "import itertools\n",
+    "\n",
+    "from pgmpy.inference.base import Inference\n",
+    "from pgmpy.factors.discrete import factor_product\n",
+    "\n",
+    "class SimpleInference(Inference):\n",
+    "    # By inheriting Inference we can use self.model, self.factors and self.cardinality in our class\n",
+    "    def query(self, var, evidence):\n",
+    "        # self.factors is a dict of the form of {node: [factors_involving_node]}\n",
+    "        factors_list = set(itertools.chain(*self.factors.values()))\n",
+    "        product = factor_product(*factors_list)\n",
+    "        reduced_prod = product.reduce(evidence, inplace=False)\n",
+    "        reduced_prod.normalize()\n",
+    "        var_to_marg = set(self.model.nodes()) - set(var) - set([state[0] for state in evidence])\n",
+    "        marg_prod = reduced_prod.marginalize(var_to_marg, inplace=False)\n",
+    "        return marg_prod"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "# Defining a model\n",
+    "\n",
+    "from pgmpy.models import BayesianModel\n",
+    "from pgmpy.factors.discrete import TabularCPD\n",
+    "\n",
+    "model = BayesianModel([('A', 'J'), ('R', 'J'), ('J', 'Q'), ('J', 'L'), ('G', 'L')])\n",
+    "cpd_a = TabularCPD('A', 2, values=[[0.2], [0.8]])\n",
+    "cpd_r = TabularCPD('R', 2, values=[[0.4], [0.6]])\n",
+    "cpd_j = TabularCPD('J', 2, values=[[0.9, 0.6, 0.7, 0.1],\n",
+    "                                   [0.1, 0.4, 0.3, 0.9]],\n",
+    "                  evidence=['A', 'R'], evidence_card=[2, 2])\n",
+    "cpd_q = TabularCPD('Q', 2, values=[[0.9, 0.2], [0.1, 0.8]],\n",
+    "                  evidence=['J'], evidence_card=[2])\n",
+    "cpd_l = TabularCPD('L', 2, values=[[0.9, 0.45, 0.8, 0.1],\n",
+    "                                   [0.1, 0.55, 0.2, 0.9]],\n",
+    "                  evidence=['J', 'G'], evidence_card=[2, 2])\n",
+    "cpd_g = TabularCPD('G', 2, values=[[0.6], [0.4]])\n",
+    "\n",
+    "model.add_cpds(cpd_a, cpd_r, cpd_j, cpd_q, cpd_l, cpd_g)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "# Doing inference with our SimpleInference\n",
+    "\n",
+    "infer = SimpleInference(model)\n",
+    "a = infer.query(var=['A'], evidence=[('J', 0), ('R', 1)])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "+-----+----------+\n",
+      "| A   |   phi(A) |\n",
+      "|-----+----------|\n",
+      "| A_0 |   0.6000 |\n",
+      "| A_1 |   0.4000 |\n",
+      "+-----+----------+\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(a)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "+-----+----------+\n",
+      "| A   |   phi(A) |\n",
+      "|-----+----------|\n",
+      "| A_0 |   0.6000 |\n",
+      "| A_1 |   0.4000 |\n",
+      "+-----+----------+\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Comparing the results with Variable Elimination Algorithm\n",
+    "\n",
+    "from pgmpy.inference import VariableElimination\n",
+    "\n",
+    "infer = VariableElimination(model)\n",
+    "result = infer.query(['A'], evidence={'J': 0, 'R': 1})\n",
+    "print(result['A'])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Similarly we can also create new classes for Factor or CPDs and add them to networks and do inference over it or can write a new estimator class."
+   ]
+  }
+ ],
+ "metadata": {
+  "anaconda-cloud": {},
+  "kernelspec": {
+   "display_name": "Python [conda env:ccns]",
+   "language": "python",
+   "name": "conda-env-ccns-py"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 2
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython2",
+   "version": "2.7.12"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/examples/Writing an inference algorithm.ipynb b/examples/Writing an inference algorithm.ipynb
deleted file mode 100644
index dfe08e87..00000000
--- a/examples/Writing an inference algorithm.ipynb
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "anaconda-cloud": {},
-  "kernelspec": {
-   "display_name": "Python [conda env:ccns]",
-   "language": "python",
-   "name": "conda-env-ccns-py"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 2
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython2",
-   "version": "2.7.12"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 1
-}
diff --git a/pgmpy/base/DirectedGraph.py b/pgmpy/base/DirectedGraph.py
index 8f457aac..61c75750 100644
--- a/pgmpy/base/DirectedGraph.py
+++ b/pgmpy/base/DirectedGraph.py
@@ -9,11 +9,11 @@ from pgmpy.base import UndirectedGraph
 
 class DirectedGraph(nx.DiGraph):
     """
-    Base class for directed graphs.
+    Base class for all Directed Graphical Models.
 
-    Directed graph assumes that all the nodes in graph are either random
-    variables, factors or clusters of random variables and edges in the graph
-    are dependencies between these random variables.
+    Each node in the graph can represent either a random variable, `Factor`,
+    or a cluster of random variables. Edges in the graph represent the
+    dependencies between these.
 
     Parameters
     ----------
@@ -28,18 +28,18 @@ class DirectedGraph(nx.DiGraph):
     >>> from pgmpy.base import DirectedGraph
     >>> G = DirectedGraph()
 
-    G can be grown in several ways
+    G can be grown in several ways:
 
     **Nodes:**
 
     Add one node at a time:
 
-    >>> G.add_node('a')
+    >>> G.add_node(node='a')
 
     Add the nodes from any container (a list, set or tuple or the nodes
     from another graph).
 
-    >>> G.add_nodes_from(['a', 'b'])
+    >>> G.add_nodes_from(nodes=['a', 'b'])
 
     **Edges:**
 
@@ -47,14 +47,14 @@ class DirectedGraph(nx.DiGraph):
 
     Add one edge,
 
-    >>> G.add_edge('a', 'b')
+    >>> G.add_edge(u='a', v='b')
 
     a list of edges,
 
-    >>> G.add_edges_from([('a', 'b'), ('b', 'c')])
+    >>> G.add_edges_from(ebunch=[('a', 'b'), ('b', 'c')])
 
     If some edges connect nodes not yet in the model, the nodes
-    are added automatically.  There are no errors when adding
+    are added automatically. There are no errors when adding
     nodes or edges that already exist.
 
     **Shortcuts:**
@@ -70,68 +70,131 @@ class DirectedGraph(nx.DiGraph):
     def __init__(self, ebunch=None):
         super(DirectedGraph, self).__init__(ebunch)
 
-    def add_node(self, node, **kwargs):
+    def add_node(self, node, weight=None):
         """
-        Add a single node to the Graph.
+        Adds a single node to the Graph.
 
         Parameters
         ----------
-        node: node
-            A node can be any hashable Python object.
+        node: str, int, or any hashable python object.
+            The node to add to the graph.
+
+        weight: int, float
+            The weight of the node.
 
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
         >>> G = DirectedGraph()
-        >>> G.add_node('A')
+        >>> G.add_node(node='A')
+        >>> G.nodes()
+        ['A']
+
+        Adding a node with some weight.
+        >>> G.add_node(node='B', weight=0.3)
+
+        The weight of these nodes can be accessed as:
+        >>> G.node['B']
+        {'weight': 0.3}
+        >>> G.node['A']
+        {'weight': None}
         """
-        super(DirectedGraph, self).add_node(node, **kwargs)
+        super(DirectedGraph, self).add_node(node, weight=weight)
 
-    def add_nodes_from(self, nodes, **kwargs):
+    def add_nodes_from(self, nodes, weights=None):
         """
         Add multiple nodes to the Graph.
 
+        **The behviour of adding weights is different than in networkx.
+
         Parameters
         ----------
         nodes: iterable container
-            A container of nodes (list, dict, set, etc.).
+            A container of nodes (list, dict, set, or any hashable python
+            object).
+
+        weights: list, tuple (default=None)
+            A container of weights (int, float). The weight value at index i
+            is associated with the variable at index i.
 
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
         >>> G = DirectedGraph()
-        >>> G.add_nodes_from(['A', 'B', 'C'])
+        >>> G.add_nodes_from(nodes=['A', 'B', 'C'])
+        >>> G.nodes()
+        ['A', 'B', 'C']
+
+        Adding nodes with weights:
+        >>> G.add_nodes_from(nodes=['D', 'E'], weights=[0.3, 0.6])
+        >>> G.node['D']
+        {'weight': 0.3}
+        >>> G.node['E']
+        {'weight': 0.6}
+        >>> G.node['A']
+        {'weight': None}
         """
-        for node in nodes:
-            self.add_node(node, **kwargs)
-
-    def add_edge(self, u, v, **kwargs):
+        nodes = list(nodes)
+
+        if weights:
+            if len(nodes) != len(weights):
+                raise ValueError("The number of elements in nodes and weights"
+                                 "should be equal.")
+            for index in range(len(nodes)):
+                self.add_node(node=nodes[index], weight=weights[index])
+        else:
+            for node in nodes:
+                self.add_node(node=node)
+
+    def add_edge(self, u, v, weight=None):
         """
         Add an edge between u and v.
 
         The nodes u and v will be automatically added if they are
-        not already in the graph
+        not already in the graph.
 
         Parameters
         ----------
-        u,v : nodes
+        u, v : nodes
             Nodes can be any hashable Python object.
 
+        weight: int, float (default=None)
+            The weight of the edge
+
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
         >>> G = DirectedGraph()
-        >>> G.add_nodes_from(['Alice', 'Bob', 'Charles'])
-        >>> G.add_edge('Alice', 'Bob')
+        >>> G.add_nodes_from(nodes=['Alice', 'Bob', 'Charles'])
+        >>> G.add_edge(u='Alice', v='Bob')
+        >>> G.nodes()
+        ['Alice', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob')]
+
+        When the node is not already present in the graph:
+        >>> G.add_edge(u='Alice', v='Ankur')
+        >>> G.nodes()
+        ['Alice', 'Ankur', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Alice', 'Ankur')]
+
+        Adding edges with weight:
+        >>> G.add_edge('Ankur', 'Maria', weight=0.1)
+        >>> G.edge['Ankur']['Maria']
+        {'weight': 0.1}
         """
-        super(DirectedGraph, self).add_edge(u, v, **kwargs)
+        super(DirectedGraph, self).add_edge(u, v, weight=weight)
 
-    def add_edges_from(self, ebunch, **kwargs):
+    def add_edges_from(self, ebunch, weights=None):
         """
         Add all the edges in ebunch.
 
         If nodes referred in the ebunch are not already present, they
-        will be automatically added. Node names should be strings.
+        will be automatically added. Node names can be any hashable python
+        object.
+
+        **The behavior of adding weights is different than networkx.
 
         Parameters
         ----------
@@ -139,20 +202,55 @@ class DirectedGraph(nx.DiGraph):
             Each edge given in the container will be added to the graph.
             The edges must be given as 2-tuples (u, v).
 
+        weights: list, tuple (default=None)
+            A container of weights (int, float). The weight value at index i
+            is associated with the edge at index i.
+
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
         >>> G = DirectedGraph()
-        >>> G.add_nodes_from(['Alice', 'Bob', 'Charles'])
-        >>> G.add_edges_from([('Alice', 'Bob'), ('Bob', 'Charles')])
+        >>> G.add_nodes_from(nodes=['Alice', 'Bob', 'Charles'])
+        >>> G.add_edges_from(ebunch=[('Alice', 'Bob'), ('Bob', 'Charles')])
+        >>> G.nodes()
+        ['Alice', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Bob', 'Charles')]
+
+        When the node is not already in the model:
+        >>> G.add_edges_from(ebunch=[('Alice', 'Ankur')])
+        >>> G.nodes()
+        ['Alice', 'Bob', 'Charles', 'Ankur']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Bob', 'Charles'), ('Alice', 'Ankur')]
+
+        Adding edges with weights:
+        >>> G.add_edges_from([('Ankur', 'Maria'), ('Maria', 'Mason')],
+        ...                  weights=[0.3, 0.5])
+        >>> G.edge['Ankur']['Maria']
+        {'weight': 0.3}
+        >>> G.edge['Maria']['Mason']
+        {'weight': 0.5}
         """
-        for edge in ebunch:
-            self.add_edge(*edge, **kwargs)
+        ebunch = list(ebunch)
+
+        if weights:
+            if len(ebunch) != len(weights):
+                raise ValueError("The number of elements in ebunch and weights"
+                                 "should be equal")
+            for index in range(len(ebunch)):
+                self.add_edge(ebunch[index][0], ebunch[index][1],
+                              weight=weights[index])
+        else:
+            for edge in ebunch:
+                self.add_edge(edge[0], edge[1])
 
     def get_parents(self, node):
         """
         Returns a list of parents of node.
 
+        Throws an error if the node is not present in the graph.
+
         Parameters
         ----------
         node: string, int or any hashable python object.
@@ -161,8 +259,8 @@ class DirectedGraph(nx.DiGraph):
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
-        >>> G = DirectedGraph([('diff', 'grade'), ('intel', 'grade')])
-        >>> G.parents('grade')
+        >>> G = DirectedGraph(ebunch=[('diff', 'grade'), ('intel', 'grade')])
+        >>> G.parents(node='grade')
         ['diff', 'intel']
         """
         return self.predecessors(node)
@@ -178,7 +276,7 @@ class DirectedGraph(nx.DiGraph):
         Examples
         --------
         >>> from pgmpy.base import DirectedGraph
-        >>> G = DirectedGraph([('diff', 'grade'), ('intel', 'grade')])
+        >>> G = DirectedGraph(ebunch=[('diff', 'grade'), ('intel', 'grade')])
         >>> moral_graph = G.moralize()
         >>> moral_graph.edges()
         [('intel', 'grade'), ('intel', 'diff'), ('grade', 'diff')]
@@ -186,6 +284,7 @@ class DirectedGraph(nx.DiGraph):
         moral_graph = UndirectedGraph(self.to_undirected().edges())
 
         for node in self.nodes():
-            moral_graph.add_edges_from(itertools.combinations(self.get_parents(node), 2))
+            moral_graph.add_edges_from(
+                itertools.combinations(self.get_parents(node), 2))
 
         return moral_graph
diff --git a/pgmpy/base/UndirectedGraph.py b/pgmpy/base/UndirectedGraph.py
index 5b51cf3a..6a3ac9ef 100644
--- a/pgmpy/base/UndirectedGraph.py
+++ b/pgmpy/base/UndirectedGraph.py
@@ -9,9 +9,9 @@ class UndirectedGraph(nx.Graph):
     """
     Base class for all the Undirected Graphical models.
 
-    UndirectedGraph assumes that all the nodes in graph are either random
-    variables, factors or cliques of random variables and edges in the graphs
-    are interactions between these random variables, factors or clusters.
+    Each node in the graph can represent either a random variable, `Factor`,
+    or a cluster of random variables. Edges in the graph are interactions
+    between the nodes.
 
     Parameters
     ----------
@@ -68,68 +68,131 @@ class UndirectedGraph(nx.Graph):
     def __init__(self, ebunch=None):
         super(UndirectedGraph, self).__init__(ebunch)
 
-    def add_node(self, node, **kwargs):
+    def add_node(self, node, weight=None):
         """
         Add a single node to the Graph.
 
         Parameters
         ----------
-        node: node
-            A node can be any hashable Python object.
+        node: str, int, or any hashable python object.
+            The node to add to the graph.
+
+        weight: int, float
+            The weight of the node.
 
         Examples
         --------
         >>> from pgmpy.base import UndirectedGraph
         >>> G = UndirectedGraph()
-        >>> G.add_node('A')
+        >>> G.add_node(node='A')
+        >>> G.nodes()
+        ['A']
+
+        Adding a node with some weight.
+        >>> G.add_node(node='B', weight=0.3)
+
+        The weight of these nodes can be accessed as:
+        >>> G.node['B']
+        {'weight': 0.3}
+        >>> G.node['A']
+        {'weight': None}
         """
-        super(UndirectedGraph, self).add_node(node, **kwargs)
+        super(UndirectedGraph, self).add_node(node, weight=weight)
 
-    def add_nodes_from(self, nodes, **kwargs):
+    def add_nodes_from(self, nodes, weights=None):
         """
         Add multiple nodes to the Graph.
 
+        **The behaviour of adding weights is different than in networkx.
+
         Parameters
         ----------
         nodes: iterable container
-            A container of nodes (list, dict, set, etc.).
+            A container of nodes (list, dict, set, or any hashable python
+            object).
+
+        weights: list, tuple (default=None)
+            A container of weights (int, float). The weight value at index i
+            is associated with the variable at index i.
 
         Examples
         --------
         >>> from pgmpy.base import UndirectedGraph
         >>> G = UndirectedGraph()
-        >>> G.add_nodes_from(['A', 'B', 'C'])
+        >>> G.add_nodes_from(nodes=['A', 'B', 'C'])
+        >>> G.nodes()
+        ['A', 'B', 'C']
+
+        Adding nodes with weights:
+        >>> G.add_nodes_from(nodes=['D', 'E'], weights=[0.3, 0.6])
+        >>> G.node['D']
+        {'weight': 0.3}
+        >>> G.node['E']
+        {'weight': 0.6}
+        >>> G.node['A']
+        {'weight': None}
         """
-        for node in nodes:
-            self.add_node(node, **kwargs)
-
-    def add_edge(self, u, v, **kwargs):
+        nodes = list(nodes)
+
+        if weights:
+            if len(nodes) != len(weights):
+                raise ValueError("The number of elements in nodes and weights"
+                                 "should be equal.")
+            for index in range(len(nodes)):
+                self.add_node(node=nodes[index], weight=weights[index])
+        else:
+            for node in nodes:
+                self.add_node(node=node)
+
+    def add_edge(self, u, v, weight=None):
         """
         Add an edge between u and v.
 
         The nodes u and v will be automatically added if they are
-        not already in the graph
+        not already in the graph.
 
         Parameters
         ----------
-        u,v : nodes
+        u, v : nodes
             Nodes can be any hashable Python object.
 
+        weight: int, float (default=None)
+            The weight of the edge.
+
         Examples
         --------
         >>> from pgmpy.base import UndirectedGraph
         >>> G = UndirectedGraph()
-        >>> G.add_nodes_from(['Alice', 'Bob', 'Charles'])
-        >>> G.add_edge('Alice', 'Bob')
+        >>> G.add_nodes_from(nodes=['Alice', 'Bob', 'Charles'])
+        >>> G.add_edge(u='Alice', v='Bob')
+        >>> G.nodes()
+        ['Alice', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob')]
+
+        When the node is not already present in the graph:
+        >>> G.add_edge(u='Alice', v='Ankur')
+        >>> G.nodes()
+        ['Alice', 'Ankur', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Alice', 'Ankur')]
+
+        Adding edges with weight:
+        >>> G.add_edge('Ankur', 'Maria', weight=0.1)
+        >>> G.edge['Ankur']['Maria']
+        {'weight': 0.1}
         """
-        super(UndirectedGraph, self).add_edge(u, v, **kwargs)
+        super(UndirectedGraph, self).add_edge(u, v, weight=weight)
 
-    def add_edges_from(self, ebunch, **kwargs):
+    def add_edges_from(self, ebunch, weights=None):
         """
         Add all the edges in ebunch.
 
         If nodes referred in the ebunch are not already present, they
-        will be automatically added.
+        will be automatically added. Node names can be any hashable python
+        object.
+
+        **The behavior of adding weights is different than networkx.
 
         Parameters
         ----------
@@ -137,17 +200,50 @@ class UndirectedGraph(nx.Graph):
             Each edge given in the container will be added to the graph.
             The edges must be given as 2-tuples (u, v).
 
+        weights: list, tuple (default=None)
+            A container of weights (int, float). The weight value at index i
+            is associated with the edge at index i.
+
         Examples
         --------
         >>> from pgmpy.base import UndirectedGraph
         >>> G = UndirectedGraph()
-        >>> G.add_nodes_from(['Alice', 'Bob', 'Charles'])
-        >>> G.add_edges_from([('Alice', 'Bob'), ('Bob', 'Charles')])
+        >>> G.add_nodes_from(nodes=['Alice', 'Bob', 'Charles'])
+        >>> G.add_edges_from(ebunch=[('Alice', 'Bob'), ('Bob', 'Charles')])
+        >>> G.nodes()
+        ['Alice', 'Bob', 'Charles']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Bob', 'Charles')]
+
+        When the node is not already in the model:
+        >>> G.add_edges_from(ebunch=[('Alice', 'Ankur')])
+        >>> G.nodes()
+        ['Alice', 'Ankur', 'Charles', 'Bob']
+        >>> G.edges()
+        [('Alice', 'Bob'), ('Bob', 'Charles'), ('Alice', 'Ankur')]
+
+        Adding edges with weights:
+        >>> G.add_edges_from([('Ankur', 'Maria'), ('Maria', 'Mason')],
+        ...                  weights=[0.3, 0.5])
+        >>> G.edge['Ankur']['Maria']
+        {'weight': 0.3}
+        >>> G.edge['Maria']['Mason']
+        {'weight': 0.5}
         """
-        for edge in ebunch:
-            self.add_edge(*edge, **kwargs)
-
-    def check_clique(self, nodes):
+        ebunch = list(ebunch)
+
+        if weights:
+            if len(ebunch) != len(weights):
+                raise ValueError("The number of elements in ebunch and weights"
+                                 "should be equal")
+            for index in range(len(ebunch)):
+                self.add_edge(ebunch[index][0], ebunch[index][1],
+                              weight=weights[index])
+        else:
+            for edge in ebunch:
+                self.add_edge(edge[0], edge[1])
+
+    def is_clique(self, nodes):
         """
         Check if the given nodes form a clique.
 
@@ -155,6 +251,22 @@ class UndirectedGraph(nx.Graph):
         ----------
         nodes: list, array-like
             List of nodes to check if they are a part of any clique.
+
+        Examples
+        --------
+        >>> from pgmpy.base import UndirectedGraph
+        >>> G = UndirectedGraph(ebunch=[('A', 'B'), ('C', 'B'), ('B', 'D'),
+                                        ('B', 'E'), ('D', 'E'), ('E', 'F'),
+                                        ('D', 'F'), ('B', 'F')])
+        >>> G.is_clique(nodes=['A', 'B', 'C', 'D'])
+        False
+        >>> G.is_clique(nodes=['B', 'D', 'E', 'F'])
+        True
+
+        Since B, D, E and F are clique, any subset of these should also
+        be clique.
+        >>> G.is_clique(nodes=['D', 'E', 'B'])
+        True
         """
         for node1, node2 in itertools.combinations(nodes, 2):
             if not self.has_edge(node1, node2):
@@ -163,14 +275,21 @@ class UndirectedGraph(nx.Graph):
 
     def is_triangulated(self):
         """
-        Checks whether the undirected graph is triangulated or not.
+        Checks whether the undirected graph is triangulated (also known
+        as chordal) or not.
+
+        Chordal Graph: A chordal graph is one in which all cycles of four
+                       or more vertices have a chord.
 
         Examples
         --------
         >>> from pgmpy.base import UndirectedGraph
         >>> G = UndirectedGraph()
-        >>> G.add_edges_from([('x1', 'x2'), ('x1', 'x3'), ('x1', 'x4'),
-        ...                   ('x2', 'x4'), ('x3', 'x4')])
+        >>> G.add_edges_from(ebunch=[('x1', 'x2'), ('x1', 'x3'),
+        ...                          ('x2', 'x4'), ('x3', 'x4')])
+        >>> G.is_triangulated()
+        False
+        >>> G.add_edge(u='x1', v='x4')
         >>> G.is_triangulated()
         True
         """


Test patch:
diff --git a/pgmpy/tests/test_base/test_DirectedGraph.py b/pgmpy/tests/test_base/test_DirectedGraph.py
index 27e8afe4..6db9177e 100644
--- a/pgmpy/tests/test_base/test_DirectedGraph.py
+++ b/pgmpy/tests/test_base/test_DirectedGraph.py
@@ -1,8 +1,9 @@
 #!/usr/bin/env python3
 
+import unittest
+
 from pgmpy.base import DirectedGraph
 import pgmpy.tests.help_functions as hf
-import unittest
 
 
 class TestDirectedGraphCreation(unittest.TestCase):
@@ -32,6 +33,19 @@ class TestDirectedGraphCreation(unittest.TestCase):
     def test_add_nodes_from_non_string(self):
         self.graph.add_nodes_from([1, 2, 3, 4])
 
+    def test_add_node_weight(self):
+        self.graph.add_node('weighted_a', 0.3)
+        self.assertEqual(self.graph.node['weighted_a']['weight'], 0.3)
+
+    def test_add_nodes_from_weight(self):
+        self.graph.add_nodes_from(['weighted_b', 'weighted_c'], [0.5, 0.6])
+        self.assertEqual(self.graph.node['weighted_b']['weight'], 0.5)
+        self.assertEqual(self.graph.node['weighted_c']['weight'], 0.6)
+
+        self.graph.add_nodes_from(['e', 'f'])
+        self.assertEqual(self.graph.node['e']['weight'], None)
+        self.assertEqual(self.graph.node['f']['weight'], None)
+
     def test_add_edge_string(self):
         self.graph.add_edge('d', 'e')
         self.assertListEqual(sorted(self.graph.nodes()), ['d', 'e'])
@@ -60,6 +74,18 @@ class TestDirectedGraphCreation(unittest.TestCase):
     def test_add_edges_from_nonstring(self):
         self.graph.add_edges_from([(1, 2), (2, 3)])
 
+    def test_add_edge_weight(self):
+        self.graph.add_edge('a', 'b', weight=0.3)
+        self.assertEqual(self.graph.edge['a']['b']['weight'], 0.3)
+
+    def test_add_edges_from_weight(self):
+        self.graph.add_edges_from([('b', 'c'), ('c', 'd')], weights=[0.5, 0.6])
+        self.assertEqual(self.graph.edge['b']['c']['weight'], 0.5)
+        self.assertEqual(self.graph.edge['c']['d']['weight'], 0.6)
+
+        self.graph.add_edges_from([('e', 'f')])
+        self.assertEqual(self.graph.edge['e']['f']['weight'], None)
+
     def test_update_node_parents_bm_constructor(self):
         self.graph = DirectedGraph([('a', 'b'), ('b', 'c')])
         self.assertListEqual(self.graph.predecessors('a'), [])
@@ -80,14 +106,13 @@ class TestDirectedGraphCreation(unittest.TestCase):
 class TestDirectedGraphMoralization(unittest.TestCase):
     def setUp(self):
         self.graph = DirectedGraph()
+        self.graph.add_edges_from([('diff', 'grade'), ('intel', 'grade')])
 
     def test_get_parents(self):
-        self.graph.add_edges_from([('diff', 'grade'), ('intel', 'grade')])
         self.assertListEqual(sorted(self.graph.get_parents('grade')),
                              ['diff', 'intel'])
 
     def test_moralize(self):
-        self.graph.add_edges_from([('diff', 'grade'), ('intel', 'grade')])
         moral_graph = self.graph.moralize()
         self.assertListEqual(hf.recursive_sorted(moral_graph.edges()),
                              [['diff', 'grade'], ['diff', 'intel'],
diff --git a/pgmpy/tests/test_base/test_UndirectedGraph.py b/pgmpy/tests/test_base/test_UndirectedGraph.py
index 0719eb61..b7cf34a7 100644
--- a/pgmpy/tests/test_base/test_UndirectedGraph.py
+++ b/pgmpy/tests/test_base/test_UndirectedGraph.py
@@ -31,6 +31,19 @@ class TestUndirectedGraphCreation(unittest.TestCase):
         self.assertListEqual(sorted(self.graph.nodes()),
                              ['a', 'b', 'c', 'd'])
 
+    def test_add_node_with_weight(self):
+        self.graph.add_node('a')
+        self.graph.add_node('weight_a', weight=0.3)
+        self.assertEqual(self.graph.node['weight_a']['weight'], 0.3)
+        self.assertEqual(self.graph.node['a']['weight'], None)
+
+    def test_add_nodes_from_with_weight(self):
+        self.graph.add_node(1)
+        self.graph.add_nodes_from(['weight_b', 'weight_c'], weights=[0.3, 0.5])
+        self.assertEqual(self.graph.node['weight_b']['weight'], 0.3)
+        self.assertEqual(self.graph.node['weight_c']['weight'], 0.5)
+        self.assertEqual(self.graph.node[1]['weight'], None)
+
     def test_add_nodes_from_non_string(self):
         self.graph.add_nodes_from([1, 2, 3, 4])
 
@@ -69,3 +82,20 @@ class TestUndirectedGraphCreation(unittest.TestCase):
 
     def tearDown(self):
         del self.graph
+
+class TestUndirectedGraphMethods(unittest.TestCase):
+    def test_is_clique(self):
+        G = UndirectedGraph([('A', 'B'), ('C', 'B'), ('B', 'D'),
+                             ('B', 'E'), ('D', 'E'), ('E', 'F'),
+                             ('D', 'F'), ('B', 'F')])
+        self.assertFalse(G.is_clique(nodes=['A', 'B', 'C', 'D']))
+        self.assertTrue(G.is_clique(nodes=['B', 'D', 'E', 'F']))
+        self.assertTrue(G.is_clique(nodes=['D', 'E', 'B']))
+
+    def test_is_triangulated(self):
+        G = UndirectedGraph([('A', 'B'), ('A', 'C'),
+                             ('B', 'D'), ('C', 'D')])
+        self.assertFalse(G.is_triangulated())
+        G.add_edge('A', 'D')
+        self.assertTrue(G.is_triangulated())
+
diff --git a/pgmpy/tests/test_models/test_MarkovModel.py b/pgmpy/tests/test_models/test_MarkovModel.py
index ccfeba1b..bff0c551 100644
--- a/pgmpy/tests/test_models/test_MarkovModel.py
+++ b/pgmpy/tests/test_models/test_MarkovModel.py
@@ -347,7 +347,7 @@ class TestUndirectedGraphTriangulation(unittest.TestCase):
 
     def test_check_clique(self):
         self.graph.add_edges_from([('a', 'b'), ('b', 'c'), ('c', 'a')])
-        self.assertTrue(self.graph.check_clique(['a', 'b', 'c']))
+        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')])
diff --git a/pgmpy/tests/test_readwrite/test_BIF.py b/pgmpy/tests/test_readwrite/test_BIF.py
index b758b4a4..39560ff2 100644
--- a/pgmpy/tests/test_readwrite/test_BIF.py
+++ b/pgmpy/tests/test_readwrite/test_BIF.py
@@ -138,16 +138,22 @@ probability (  "family-out" ) { //1 variable(s) and 2 values
                           ('dog-out', 'hear-bark')]
         nodes_expected = ['bowel-problem', 'hear-bark', 'light-on',
                           'dog-out', 'family-out']
-        edge_expected = {'bowel-problem': {'dog-out': {}},
-                         'dog-out': {'hear-bark': {}},
-                         'family-out': {'dog-out': {}, 'light-on': {}},
+        edge_expected = {'bowel-problem': {'dog-out': {'weight': None}},
+                         'dog-out': {'hear-bark': {'weight': None}},
+                         'family-out': {'dog-out': {'weight': None},
+                                        'light-on': {'weight': None}},
                          'hear-bark': {},
                          'light-on': {}}
-        node_expected = {'bowel-problem': {'position': '(335, 99)'},
-                         'dog-out': {'position': '(300, 195)'},
-                         'family-out': {'position': '(257, 99)'},
-                         'hear-bark': {'position': '(296, 268)'},
-                         'light-on': {'position': '(218, 195)'}}
+        node_expected = {'bowel-problem': {'weight': None,
+                                           'position': '(335, 99)'},
+                         'dog-out': {'weight': None,
+                                     'position': '(300, 195)'},
+                         'family-out': {'weight': None,
+                                        'position': '(257, 99)'},
+                         'hear-bark': {'weight': None,
+                                       'position': '(296, 268)'},
+                         'light-on': {'weight': None,
+                                      'position': '(218, 195)'}}
         cpds_expected = [np.array([[0.01],
                                    [0.99]]),
                          np.array([[0.99, 0.97, 0.9, 0.3],
diff --git a/pgmpy/tests/test_readwrite/test_ProbModelXML.py b/pgmpy/tests/test_readwrite/test_ProbModelXML.py
index 65877cb2..60400afa 100644
--- a/pgmpy/tests/test_readwrite/test_ProbModelXML.py
+++ b/pgmpy/tests/test_readwrite/test_ProbModelXML.py
@@ -583,16 +583,24 @@ class TestProbModelXMLReaderString(unittest.TestCase):
                                         'type': 'finiteStates',
                                         'Coordinates': {'y': '152', 'x': '421'},
                                         'AdditionalProperties': {'Title': 'L', 'Relevance': '7.0'}}}
-        edge_expected = {'LungCancer': {'TuberculosisOrCancer': {'directed': 'true'}},
-                         'Smoker': {'LungCancer': {'directed': 'true'},
-                                    'Bronchitis': {'directed': 'true'}},
+        edge_expected = {'LungCancer': {'TuberculosisOrCancer': {'weight': None,
+                                                                 'directed': 'true'}},
+                         'Smoker': {'LungCancer': {'weight': None,
+                                                   'directed': 'true'},
+                                    'Bronchitis': {'weight': None,
+                                                   'directed': 'true'}},
                          'Dyspnea': {},
                          'X-ray': {},
-                         'VisitToAsia': {'Tuberculosis': {'directed': 'true'}},
-                         'TuberculosisOrCancer': {'X-ray': {'directed': 'true'},
-                                                  'Dyspnea': {'directed': 'true'}},
-                         'Bronchitis': {'Dyspnea': {'directed': 'true'}},
-                         'Tuberculosis': {'TuberculosisOrCancer': {'directed': 'true'}}}
+                         'VisitToAsia': {'Tuberculosis': {'weight': None,
+                                                          'directed': 'true'}},
+                         'TuberculosisOrCancer': {'X-ray': {'weight': None,
+                                                            'directed': 'true'},
+                                                  'Dyspnea': {'weight': None,
+                                                              'directed': 'true'}},
+                         'Bronchitis': {'Dyspnea': {'weight': None,
+                                                    'directed': 'true'}},
+                         'Tuberculosis': {'TuberculosisOrCancer': {'weight': None,
+                                                                   'directed': 'true'}}}
 
         cpds_expected = [np.array([[0.95, 0.05], [0.02, 0.98]]),
                          np.array([[0.7, 0.3], [0.4,  0.6]]),
diff --git a/pgmpy/tests/test_readwrite/test_UAI.py b/pgmpy/tests/test_readwrite/test_UAI.py
index 7f3c4547..604dd08f 100644
--- a/pgmpy/tests/test_readwrite/test_UAI.py
+++ b/pgmpy/tests/test_readwrite/test_UAI.py
@@ -55,12 +55,13 @@ class TestUAIReader(unittest.TestCase):
 
     def test_get_model(self):
         model = self.reader_string.get_model()
-        edge_expected = {'var_2': {'var_0': {},
-                                   'var_1': {}},
-                         'var_0': {'var_2': {},
-                                   'var_1': {}},
-                         'var_1': {'var_2': {},
-                                   'var_0': {}}}
+        edge_expected = {
+            'var_2': {'var_0': {'weight': None},
+                      'var_1': {'weight': None}},
+            'var_0': {'var_2': {'weight': None},
+                      'var_1': {'weight': None}},
+            'var_1': {'var_2': {'weight': None},
+                      'var_0': {'weight': None}}}
         self.assertListEqual(sorted(model.nodes()), sorted(['var_0', 'var_2', 'var_1']))
         self.assertDictEqual(model.edge, edge_expected)