Repository: pgmpy/pgmpy

Issue:
Manually specified elimination order throws an error
Since network pruning has now been added, if the elimination order is specified over all the variables in the model, it throws an error as some of the variables don't exist in the model anymore as a result of pruning. To fix this, add a check for manually specified elimination order after pruning such that it removes all the variables which are no longer in the model.

Ref #1391

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=h1) Report
> Merging [#1402](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=desc) (2300795) into [dev](https://codecov.io/gh/pgmpy/pgmpy/commit/843f7222686d2299cbe288dfb170d60aef945be8?el=desc) (843f722) will **increase** coverage by `0.02%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/pgmpy/pgmpy/pull/1402/graphs/tree.svg?width=650&height=150&src=pr&token=UaJMCdHaEF)](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              dev    #1402      +/-   ##
==========================================
+ Coverage   93.30%   93.32%   +0.02%     
==========================================
  Files         134      134              
  Lines       13523    13536      +13     
==========================================
+ Hits        12618    12633      +15     
+ Misses        905      903       -2     
```


| [Impacted Files](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pgmpy/inference/ExactInference.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1402/diff?src=pr&el=tree#diff-cGdtcHkvaW5mZXJlbmNlL0V4YWN0SW5mZXJlbmNlLnB5) | `96.01% <100.00%> (+0.79%)` | :arrow_up: |
| [pgmpy/tests/test\_inference/test\_ExactInference.py](https://codecov.io/gh/pgmpy/pgmpy/pull/1402/diff?src=pr&el=tree#diff-cGdtcHkvdGVzdHMvdGVzdF9pbmZlcmVuY2UvdGVzdF9FeGFjdEluZmVyZW5jZS5weQ==) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=footer). Last update [843f722...2300795](https://codecov.io/gh/pgmpy/pgmpy/pull/1402?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/pgmpy/inference/ExactInference.py b/pgmpy/inference/ExactInference.py
index a9a3612b..c26da331 100644
--- a/pgmpy/inference/ExactInference.py
+++ b/pgmpy/inference/ExactInference.py
@@ -74,6 +74,7 @@ class VariableElimination(Inference):
         )
 
         # Step 1: If elimination_order is a list, verify it's correct and return.
+        # Step 1.1: Check that not of the `variables` and `evidence` is in the elimination_order.
         if hasattr(elimination_order, "__iter__") and (
             not isinstance(elimination_order, str)
         ):
@@ -87,8 +88,21 @@ class VariableElimination(Inference):
                     "Elimination order contains variables which are in"
                     " variables or evidence args"
                 )
-            else:
-                return elimination_order
+            # Step 1.2: Check if elimination_order has variables which are not in the model.
+            elif any(var not in self.model.nodes() for var in elimination_order):
+                elimination_order = list(
+                    filter(lambda t: t in self.model.nodes(), elimination_order)
+                )
+
+            # Step 1.3: Check if the elimination_order has all the variables that need to be eliminated.
+            elif to_eliminate != set(elimination_order):
+                raise ValueError(
+                    f"Elimination order doesn't contain all the variables"
+                    f"which need to be eliminated. The variables which need to"
+                    f"be eliminated are {to_eliminate}"
+                )
+
+            return elimination_order
 
         # Step 2: If elimination order is None or a Markov model, return a random order.
         elif (elimination_order is None) or (not isinstance(self.model, BayesianModel)):


Test patch:
diff --git a/pgmpy/tests/test_inference/test_ExactInference.py b/pgmpy/tests/test_inference/test_ExactInference.py
index 48bfdfbe..e2fdf887 100644
--- a/pgmpy/tests/test_inference/test_ExactInference.py
+++ b/pgmpy/tests/test_inference/test_ExactInference.py
@@ -183,6 +183,47 @@ class TestVariableElimination(unittest.TestCase):
             evidence=["J"],
         )
 
+    def test_elimination_order(self):
+        # Check all the heuristics give the same results.
+        for elimination_order in [
+            "WeightedMinFill",
+            "MinNeighbors",
+            "MinWeight",
+            "MinFill",
+        ]:
+            query_result = self.bayesian_inference.query(
+                ["J"], elimination_order=elimination_order
+            )
+            self.assertEqual(
+                query_result,
+                DiscreteFactor(variables=["J"], cardinality=[2], values=[0.416, 0.584]),
+            )
+
+            query_result = self.bayesian_inference.query(
+                variables=["J"], evidence={"A": 0, "R": 1}
+            )
+            self.assertEqual(
+                query_result,
+                DiscreteFactor(variables=["J"], cardinality=[2], values=[0.6, 0.4]),
+            )
+
+        # Check when elimination order has extra variables. Because of pruning.
+        query_result = self.bayesian_inference.query(
+            ["J"], elimination_order=["A", "R", "L", "Q", "G"]
+        )
+        self.assertEqual(
+            query_result,
+            DiscreteFactor(variables=["J"], cardinality=[2], values=[0.416, 0.584]),
+        )
+
+        # Check for when elimination order doesn't have all the variables
+        self.assertRaises(
+            ValueError,
+            self.bayesian_inference.query,
+            variables=["J"],
+            elimination_order=["A"],
+        )
+
     def test_induced_graph(self):
         induced_graph = self.bayesian_inference.induced_graph(
             ["G", "Q", "A", "J", "L", "R"]