Repository: pgmpy/pgmpy

Issue:
Adding a dictionary of node:states to models
I often find myself needing to iterate over the states of a node.
For this, it would be useful to have a dictionary that maps the names of the nodes of a model to 

I have resorted to the workaround of initializing this myself when loading a model.

```
from pgmpy.utils import get_example_model
model = get_example_model('asia')

def get_states(model, node):
  phi = model.get_cpds(node)
  n_states = phi.get_cardinality([node])[node]
  states = [phi.get_state_names(node, i) for i in range(n_states)]
  return states

model.states = {node : get_states(model, node) for node in model.nodes}
model.states
```

1. Is this the easiest way to get access to the states?
2. Can we add a `@property` method called `states` to the base `BayesianModel` class reproducing this functionality? I'd be happy to do the PR myself if this is agreed to be a good approach. 

Discussion:
ankurankan: @Jsevillamol Black seems to be complaining about your changes. If you run `black .` in pgmpy home dir and commit the changes, it should fix it. Also, I would suggest a couple of other things:
1. The docstring needs to be formatted according to the numpydoc style, otherwise, our documentation generator would fail. You can look at the other method docstrings for examples.
2. Could you maybe add a test for the new method?
Jsevillamol: Thank you for helping me through this @ankurankan !

I have amended the commit as you suggested.
codecov[bot]: # [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) Report
> Merging [#1470](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) (a24c5f9) into [dev](https://codecov.io/gh/pgmpy/pgmpy/commit/4a0c160626d703ea7ae9cd4ed40119ecc7df25b6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) (4a0c160) will **decrease** coverage by `0.01%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/graphs/tree.svg?width=650&height=150&src=pr&token=UaJMCdHaEF&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)

```diff
@@            Coverage Diff             @@
##              dev    #1470      +/-   ##
==========================================
- Coverage   94.71%   94.70%   -0.02%     
==========================================
  Files         143      143              
  Lines       16415    16367      -48     
==========================================
- Hits        15548    15500      -48     
  Misses        867      867              
```


| [Impacted Files](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) | Coverage Δ | |
|---|---|---|
| [pgmpy/models/BayesianNetwork.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvbW9kZWxzL0JheWVzaWFuTmV0d29yay5weQ==) | `92.70% <100.00%> (+0.10%)` | :arrow_up: |
| [pgmpy/tests/test\_models/test\_BayesianNetwork.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9tb2RlbHMvdGVzdF9CYXllc2lhbk5ldHdvcmsucHk=) | `100.00% <100.00%> (ø)` | |
| [.../test\_models/test\_LinearGaussianBayesianNetwork.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9tb2RlbHMvdGVzdF9MaW5lYXJHYXVzc2lhbkJheWVzaWFuTmV0d29yay5weQ==) | `39.06% <0.00%> (-3.59%)` | :arrow_down: |
| [...actors/test\_continuous/test\_Linear\_Gaussain\_CPD.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9mYWN0b3JzL3Rlc3RfY29udGludW91cy90ZXN0X0xpbmVhcl9HYXVzc2Fpbl9DUEQucHk=) | `40.00% <0.00%> (-2.86%)` | :arrow_down: |
| [pgmpy/factors/continuous/discretize.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9jb250aW51b3VzL2Rpc2NyZXRpemUucHk=) | `34.21% <0.00%> (-1.69%)` | :arrow_down: |
| [pgmpy/tests/test\_estimators/test\_SEMEstimator.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9lc3RpbWF0b3JzL3Rlc3RfU0VNRXN0aW1hdG9yLnB5) | `79.66% <0.00%> (-1.59%)` | :arrow_down: |
| [pgmpy/factors/base.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9iYXNlLnB5) | `84.21% <0.00%> (-0.79%)` | :arrow_down: |
| [...gmpy/factors/distributions/GaussianDistribution.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9kaXN0cmlidXRpb25zL0dhdXNzaWFuRGlzdHJpYnV0aW9uLnB5) | `69.00% <0.00%> (-0.61%)` | :arrow_down: |
| [pgmpy/independencies/Independencies.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvaW5kZXBlbmRlbmNpZXMvSW5kZXBlbmRlbmNpZXMucHk=) | `87.39% <0.00%> (-0.41%)` | :arrow_down: |
| [pgmpy/factors/continuous/ContinuousFactor.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvZmFjdG9ycy9jb250aW51b3VzL0NvbnRpbnVvdXNGYWN0b3IucHk=) | `86.11% <0.00%> (-0.38%)` | :arrow_down: |
| ... and [15 more](https://codecov.io/gh/pgmpy/pgmpy/pull/1470/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy). Last update [4a0c160...a24c5f9](https://codecov.io/gh/pgmpy/pgmpy/pull/1470?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).

ankurankan: @Jsevillamol Thanks again for the PR. Everything looks good. 

Patch:
diff --git a/pgmpy/models/BayesianNetwork.py b/pgmpy/models/BayesianNetwork.py
index 77987214..0da60ffe 100644
--- a/pgmpy/models/BayesianNetwork.py
+++ b/pgmpy/models/BayesianNetwork.py
@@ -371,6 +371,22 @@ class BayesianNetwork(DAG):
                 cardinalities[cpd.variable] = cpd.cardinality[0]
             return cardinalities
 
+    @property
+    def states(self):
+        """
+        Returns a dictionary mapping each node to its list of possible states.
+
+        Returns
+        -------
+        state_dict: dict
+            Dictionary of nodes to possible states
+        """
+        state_names_list = [cpd.state_names for cpd in self.cpds]
+        state_dict = {
+            node: states for d in state_names_list for node, states in d.items()
+        }
+        return state_dict
+
     def check_model(self):
         """
         Check the model for various errors. This method checks for the following


Test patch:
diff --git a/pgmpy/tests/test_models/test_BayesianNetwork.py b/pgmpy/tests/test_models/test_BayesianNetwork.py
index 96bbab8c..53ede0a9 100644
--- a/pgmpy/tests/test_models/test_BayesianNetwork.py
+++ b/pgmpy/tests/test_models/test_BayesianNetwork.py
@@ -193,6 +193,11 @@ class TestBayesianModelMethods(unittest.TestCase):
             self.G1.get_cardinality(), {"diff": 2, "intel": 3, "grade": 3}
         )
 
+    def test_states(self):
+        self.assertDictEqual(
+            self.G1.states, {"diff": [0, 1], "intel": [0, 1, 2], "grade": [0, 1, 2]}
+        )
+
     def test_get_cardinality_with_node(self):
         self.assertEqual(self.G1.get_cardinality("diff"), 2)
         self.assertEqual(self.G1.get_cardinality("intel"), 3)