Repository: pgmpy/pgmpy

Issue:
state_names not handled in DBN initialize_initial_state
When I take an example from tutorial and pass `state_names` parameter, I get inconsistent result after `initialize_initial_state`.

```
from pgmpy.models import DynamicBayesianNetwork as DBN
from pgmpy.factors.discrete import TabularCPD

# Initialize a simple DBN model modeling the Weather (W), Rain (O), Temperature (T), and Humidity (H).

dbn = DBN()

# pgmpy requires the user to define the structure of the first time slice and the edges connecting the first time slice to the second time slice.
# pgmpy assumes that this structure remains constant for further time slices, i.e., it is a 2-TBN.

# Add intra-slice edges for both time slices
dbn.add_edges_from(
    [
        (("W", 0), ("O", 0)),  # Weather influences ground observation
    ]
)

# Add inter-slice edges
dbn.add_edges_from(
    [
        (("W", 0), ("W", 1)),  # Weather transition
    ]
)

# Define the parameters of the model. Again pgmpy assumes that these CPDs remain the same for future time slices.
state_names = {
    ("W", 0): ["Sunny", "Cloudy", "Rainy"],
    ("W", 1): ["Sunny", "Cloudy", "Rainy"],
    ("O", 0): ["Dry", "Wet"],
    ("O", 1): ["Dry", "Wet"],
}
# Define CPDs
# CPD for W (Weather transition)
cpd_w_0 = TabularCPD(
    variable=("W", 0),
    variable_card=3,  # Sunny, Cloudy, Rainy
    values=[[0.6], [0.3], [0.1]],  # Initial probabilities
    state_names=state_names,
)

cpd_w_1 = TabularCPD(
    variable=("W", 1),
    variable_card=3,
    evidence=[("W", 0)],
    evidence_card=[3],
    values=[
        [0.7, 0.3, 0.2],  # P(Sunny | W_0)
        [0.2, 0.4, 0.3],  # P(Cloudy | W_0)
        [0.1, 0.3, 0.5],  # P(Rainy | W_0)
    ],
    state_names=state_names,
)

# CPD for O (Ground observation)
cpd_o = TabularCPD(
    variable=("O", 0),
    variable_card=2,  # Dry, Wet
    evidence=[("W", 0)],
    evidence_card=[3],
    values=[
        [0.9, 0.6, 0.2],  # P(Dry | Sunny, Cloudy, Rainy)
        [0.1, 0.4, 0.8],  # P(Wet | Sunny, Cloudy, Rainy)
    ],
    state_names=state_names,
)

# Add CPDs to the DBN
dbn.add_cpds(cpd_w_0, cpd_w_1, cpd_o)

# After defining the model, call the initialization method that generates the required data structures for further computation.
dbn.initialize_initial_state()

# Simulate some data from the defined model.

for c in dbn.get_cpds():
    print(c.state_names)
```

```
{('W', 0): ['Sunny', 'Cloudy', 'Rainy'], ('W', 1): ['Sunny', 'Cloudy', 'Rainy'], ('O', 0): ['Dry', 'Wet'], ('O', 1): ['Dry', 'Wet']}
{('W', 0): ['Sunny', 'Cloudy', 'Rainy'], ('W', 1): ['Sunny', 'Cloudy', 'Rainy'], ('O', 0): ['Dry', 'Wet'], ('O', 1): ['Dry', 'Wet']}
{('W', 0): ['Sunny', 'Cloudy', 'Rainy'], ('W', 1): ['Sunny', 'Cloudy', 'Rainy'], ('O', 0): ['Dry', 'Wet'], ('O', 1): ['Dry', 'Wet']}
{<DynamicNode(O, 1) at 0x7fdc1215ff50>: [0, 1], <DynamicNode(W, 1) at 0x7fdd1cf35fd0>: [0, 1, 2]}
```


Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) Report
Attention: Patch coverage is `13.33333%` with `39 lines` in your changes missing coverage. Please review.
> Project coverage is 37.03%. Comparing base [(`720b0ae`)](https://app.codecov.io/gh/pgmpy/pgmpy/commit/720b0ae4fd57b4f8a9ebeb076ea860e799f4d39d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) to head [(`418d670`)](https://app.codecov.io/gh/pgmpy/pgmpy/commit/418d670cb54714afe165edaa513858daded6aa0d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).
> Report is 33 commits behind head on dev.

| [Files with missing lines](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) | Patch % | Lines |
|---|---|---|
| [...y/tests/test\_models/test\_DynamicBayesianNetwork.py](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?src=pr&el=tree&filepath=pgmpy%2Ftests%2Ftest_models%2Ftest_DynamicBayesianNetwork.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvdGVzdHMvdGVzdF9tb2RlbHMvdGVzdF9EeW5hbWljQmF5ZXNpYW5OZXR3b3JrLnB5) | 10.52% | [34 Missing :warning: ](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) |
| [pgmpy/models/DynamicBayesianNetwork.py](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?src=pr&el=tree&filepath=pgmpy%2Fmodels%2FDynamicBayesianNetwork.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy#diff-cGdtcHkvbW9kZWxzL0R5bmFtaWNCYXllc2lhbk5ldHdvcmsucHk=) | 28.57% | [5 Missing :warning: ](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy) |

> :exclamation:  There is a different number of reports uploaded between BASE (720b0ae) and HEAD (418d670). Click for more details.
> 
> <details><summary>HEAD has 16 uploads less than BASE</summary>
>
>| Flag | BASE (720b0ae) | HEAD (418d670) |
>|------|------|------|
>||18|2|
></details>

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##              dev    #1963       +/-   ##
===========================================
- Coverage   93.61%   37.03%   -56.59%     
===========================================
  Files         143      146        +3     
  Lines       18455    19050      +595     
===========================================
- Hits        17277     7055    -10222     
- Misses       1178    11995    +10817     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pgmpy/pgmpy/pull/1963?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pgmpy).


Patch:
diff --git a/pgmpy/models/DynamicBayesianNetwork.py b/pgmpy/models/DynamicBayesianNetwork.py
index 05a92c65..df2dafb9 100644
--- a/pgmpy/models/DynamicBayesianNetwork.py
+++ b/pgmpy/models/DynamicBayesianNetwork.py
@@ -640,6 +640,8 @@ class DynamicBayesianNetwork(DAG):
         for cpd in self.cpds:
             temp_var = DynamicNode(cpd.variable[0], 1 - cpd.variable[1])
             parents = self.get_parents(temp_var)
+            state_names = self.states.copy()
+            state_names[temp_var] = state_names[cpd.variable]
             if not any(x.variable == temp_var for x in self.cpds):
                 if all(x[1] == parents[0][1] for x in parents):
                     if parents:
@@ -652,6 +654,7 @@ class DynamicBayesianNetwork(DAG):
                             ),
                             parents,
                             evidence_card,
+                            state_names.copy(),
                         )
                     else:
                         if cpd.get_evidence():
@@ -662,12 +665,14 @@ class DynamicBayesianNetwork(DAG):
                                 temp_var,
                                 cpd.variable_card,
                                 np.reshape(initial_cpd.values, (2, -1)),
+                                state_names=state_names.copy(),
                             )
                         else:
                             new_cpd = TabularCPD(
                                 temp_var,
                                 cpd.variable_card,
                                 np.reshape(cpd.values, (2, -1)),
+                                state_names=state_names.copy(),
                             )
                     self.add_cpds(new_cpd)
             self.check_model()
@@ -1174,3 +1179,19 @@ class DynamicBayesianNetwork(DAG):
             )
             sampled = pd.concat((remaining_df, new_samples), axis=1)
         return self._postprocess(sampled)
+
+    @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


Test patch:
diff --git a/pgmpy/tests/test_models/test_DynamicBayesianNetwork.py b/pgmpy/tests/test_models/test_DynamicBayesianNetwork.py
index 93a1edaf..c6b16091 100644
--- a/pgmpy/tests/test_models/test_DynamicBayesianNetwork.py
+++ b/pgmpy/tests/test_models/test_DynamicBayesianNetwork.py
@@ -1462,3 +1462,174 @@ class TestDBNSampling(unittest.TestCase):
                             atol=0.08,
                         )
                     )
+
+
+class TestDBNWithStateName(unittest.TestCase):
+    def setUp(self):
+        self.network = DBN()
+        self.state_names = {
+            ("G", 0): ["A", "B", "C"],
+            ("D", 0): ["Easy", "Difficult"],
+            ("I", 0): ["Intelligent", "Dumb"],
+            ("G", 1): ["A", "B", "C"],
+            ("D", 1): ["Easy", "Difficult"],
+            ("I", 1): ["Intelligent", "Dumb"],
+        }
+        self.grade_cpd = TabularCPD(
+            ("G", 0),
+            3,
+            values=[[0.3, 0.05, 0.8, 0.5], [0.4, 0.25, 0.1, 0.3], [0.3, 0.7, 0.1, 0.2]],
+            evidence=[("D", 0), ("I", 0)],
+            evidence_card=[2, 2],
+            state_names=self.state_names,
+        )
+        self.d_i_cpd = TabularCPD(
+            ("D", 1),
+            2,
+            values=[[0.6, 0.3], [0.4, 0.7]],
+            evidence=[("D", 0)],
+            evidence_card=[2],
+            state_names=self.state_names,
+        )
+        self.diff_cpd = TabularCPD(
+            ("D", 0), 2, values=[[0.6], [0.4]], state_names=self.state_names
+        )
+        self.intel_cpd = TabularCPD(
+            ("I", 0), 2, values=[[0.7], [0.3]], state_names=self.state_names
+        )
+        self.i_i_cpd = TabularCPD(
+            ("I", 1),
+            2,
+            values=[[0.5, 0.4], [0.5, 0.6]],
+            evidence=[("I", 0)],
+            evidence_card=[2],
+            state_names=self.state_names,
+        )
+        self.grade_1_cpd = TabularCPD(
+            ("G", 1),
+            3,
+            values=[[0.3, 0.05, 0.8, 0.5], [0.4, 0.25, 0.1, 0.3], [0.3, 0.7, 0.1, 0.2]],
+            evidence=[("D", 1), ("I", 1)],
+            evidence_card=[2, 2],
+            state_names=self.state_names,
+        )
+
+    def test_get_cpds(self):
+        self.network.add_edges_from(
+            [
+                (("D", 0), ("G", 0)),
+                (("I", 0), ("G", 0)),
+                (("D", 0), ("D", 1)),
+                (("I", 0), ("I", 1)),
+            ]
+        )
+        self.network.add_cpds(
+            self.grade_cpd, self.d_i_cpd, self.diff_cpd, self.intel_cpd, self.i_i_cpd
+        )
+        self.network.initialize_initial_state()
+        self.assertEqual(
+            {cpd.variable for cpd in self.network.get_cpds()},
+            {("I", 1), ("D", 0), ("G", 1), ("I", 0), ("G", 0), ("D", 1)},
+        )
+        self.assertEqual(
+            {cpd.variable for cpd in self.network.get_cpds(time_slice=[0, 1])},
+            {("I", 1), ("D", 0), ("G", 1), ("I", 0), ("G", 0), ("D", 1)},
+        )
+        self.assertEqual(
+            set(self.network.get_cpds(time_slice=0)),
+            set([self.diff_cpd, self.intel_cpd, self.grade_cpd]),
+        )
+        self.assertEqual(
+            {cpd.variable for cpd in self.network.get_cpds(time_slice=1)},
+            {("D", 1), ("I", 1), ("G", 1)},
+        )
+        self.assertEqual(self.network.states, self.state_names)
+
+    def test_get_constant_bn(self):
+        self.network.add_edges_from(
+            [
+                (("D", 0), ("G", 0)),
+                (("I", 0), ("G", 0)),
+                (("D", 0), ("D", 1)),
+                (("I", 0), ("I", 1)),
+                (("D", 1), ("G", 1)),
+                (("I", 1), ("G", 1)),
+            ]
+        )
+        self.network.add_cpds(
+            self.grade_cpd,
+            self.d_i_cpd,
+            self.diff_cpd,
+            self.intel_cpd,
+            self.i_i_cpd,
+            self.grade_1_cpd,
+        )
+
+        self.assertEqual(self.network.states, self.state_names)
+        self.network.initialize_initial_state()
+
+        bn = self.network.get_constant_bn(t_slice=0)
+        self.assertEqual(set(bn.nodes()), {"D_0", "I_0", "G_0", "D_1", "I_1", "G_1"})
+        self.assertEqual(
+            set(bn.edges()),
+            {
+                ("D_0", "G_0"),
+                ("I_0", "G_0"),
+                ("D_0", "D_1"),
+                ("I_0", "I_1"),
+                ("D_1", "G_1"),
+                ("I_1", "G_1"),
+            },
+        )
+        self.assertTrue(bn.check_model())
+        self.assertEqual(
+            bn.states,
+            {
+                "G_0": ["A", "B", "C"],
+                "D_0": ["Easy", "Difficult"],
+                "I_0": ["Intelligent", "Dumb"],
+                "D_1": ["Easy", "Difficult"],
+                "I_1": ["Intelligent", "Dumb"],
+                "G_1": ["A", "B", "C"],
+            },
+        )
+        bn = self.network.get_constant_bn(t_slice=1)
+        self.assertEqual(set(bn.nodes()), {"D_1", "I_1", "G_1", "D_2", "I_2", "G_2"})
+        self.assertEqual(
+            set(bn.edges()),
+            {
+                ("D_1", "G_1"),
+                ("I_1", "G_1"),
+                ("D_1", "D_2"),
+                ("I_1", "I_2"),
+                ("D_2", "G_2"),
+                ("I_2", "G_2"),
+            },
+        )
+        self.assertTrue(bn.check_model())
+
+        bn = self.network.get_constant_bn(t_slice=2)
+        self.assertEqual(set(bn.nodes()), {"D_2", "I_2", "G_2", "D_3", "I_3", "G_3"})
+        self.assertEqual(
+            set(bn.edges()),
+            {
+                ("D_2", "G_2"),
+                ("I_2", "G_2"),
+                ("D_2", "D_3"),
+                ("I_2", "I_3"),
+                ("D_3", "G_3"),
+                ("I_3", "G_3"),
+            },
+        )
+        self.assertTrue(bn.check_model())
+        self.assertEqual(
+            bn.states,
+            {
+                "G_2": ["A", "B", "C"],
+                "D_2": ["Easy", "Difficult"],
+                "I_2": ["Intelligent", "Dumb"],
+                "D_3": ["Easy", "Difficult"],
+                "I_3": ["Intelligent", "Dumb"],
+                "G_3": ["A", "B", "C"],
+            },
+        )