From ebe20df76913a689e23c3156091877532cdd2a86 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 15:29:13 +0200 Subject: [PATCH 01/17] add assert False to see if triggered --- cpmpy/solvers/exact.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpmpy/solvers/exact.py b/cpmpy/solvers/exact.py index b2c09ea17..25ef29950 100644 --- a/cpmpy/solvers/exact.py +++ b/cpmpy/solvers/exact.py @@ -648,7 +648,8 @@ def get_core(self): return [self.assumption_dict[i][1] for i in self.xct_solver.getLastCore()] @classmethod - def mus_native(cls, soft, hard=[]): + def mus_native(cls, soft, hard=[]): + assert False # Create assumption variables and model with hard + (assumption -> soft) from cpmpy.tools.explain.utils import make_assump_model # avoid circular import m, soft, assumptions = make_assump_model(soft, hard) From fcea9f049d5d021954aabeae7ed96635230f537c Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 15:34:10 +0200 Subject: [PATCH 02/17] add extra assert False --- tests/test_tools_mus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 7e7ae1a1a..14f0392fc 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -24,6 +24,8 @@ def test_circular(self): assert set(self.mus_func(cons)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) + assert False + def test_bug_191(self): """ From 803bf1ee4a6bedf55dcd99b6d703a3dcf4176d44 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 15:41:08 +0200 Subject: [PATCH 03/17] remove extra assert False --- tests/test_tools_mus.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 14f0392fc..eeeba8976 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -24,7 +24,6 @@ def test_circular(self): assert set(self.mus_func(cons)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) - assert False def test_bug_191(self): From 74afac45fceaaff1c40335e311510b6160bf398b Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 15:56:27 +0200 Subject: [PATCH 04/17] add solver argument to mus tests to trigger testing with every solver --- tests/test_tools_mus.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index eeeba8976..a47f8f28c 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -10,7 +10,7 @@ def setup_method(self): self.mus_func = mus self.naive_func = mus_naive - def test_circular(self): + def test_circular(self, solver): x = cp.intvar(0, 3, shape=4, name="x") # circular "bigger then", UNSAT cons = [ @@ -25,8 +25,7 @@ def test_circular(self): assert set(self.mus_func(cons)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) - - def test_bug_191(self): + def test_bug_191(self, solver): """ Original Bug request: https://github.com/CPMpy/cpmpy/issues/191 When assum is a single boolvar and candidates is a list (of length 1), it fails. @@ -40,7 +39,7 @@ def test_bug_191(self): mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_bug_191_many_soft(self): + def test_bug_191_many_soft(self, solver): """ Checking whether bugfix 191 doesn't break anything in the MUS tool chain, when the number of soft constraints > 1. @@ -58,7 +57,7 @@ def test_bug_191_many_soft(self): mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_wglobal(self): + def test_wglobal(self, solver): x = cp.intvar(-9, 9, name="x") y = cp.intvar(-9, 9, name="y") @@ -86,7 +85,7 @@ def test_wglobal(self): # self.assertEqual(set(self.naive_func(cons)), set(cons[:2])) @pytest.mark.requires_solver("exact") class TestNativeMusExact(TestMus): - def setup_method(self): + def setup_method(self, solver): self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver="exact") self.naive_func = mus_naive From 2a1f4046cb34cd671393c207f46a48829894e5be Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 16:01:54 +0200 Subject: [PATCH 05/17] only add solver arg to native_mus test --- tests/test_tools_mus.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index a47f8f28c..23c949b3a 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -10,7 +10,7 @@ def setup_method(self): self.mus_func = mus self.naive_func = mus_naive - def test_circular(self, solver): + def test_circular(self): x = cp.intvar(0, 3, shape=4, name="x") # circular "bigger then", UNSAT cons = [ @@ -25,7 +25,7 @@ def test_circular(self, solver): assert set(self.mus_func(cons)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) - def test_bug_191(self, solver): + def test_bug_191(self): """ Original Bug request: https://github.com/CPMpy/cpmpy/issues/191 When assum is a single boolvar and candidates is a list (of length 1), it fails. @@ -39,7 +39,7 @@ def test_bug_191(self, solver): mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_bug_191_many_soft(self, solver): + def test_bug_191_many_soft(self): """ Checking whether bugfix 191 doesn't break anything in the MUS tool chain, when the number of soft constraints > 1. @@ -84,11 +84,12 @@ def test_wglobal(self, solver): assert not cp.Model(ms).solve() # self.assertEqual(set(self.naive_func(cons)), set(cons[:2])) @pytest.mark.requires_solver("exact") -class TestNativeMusExact(TestMus): +class TestNativeMus(TestMus): def setup_method(self, solver): - self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver="exact") - self.naive_func = mus_naive - + # solvers that implement native mus + if solver in ["exact"]: + self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) + self.naive_func = mus_naive class TestQuickXplain(TestMus): From 9ea94ec521a55a21639332b743761645965125e5 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 16:53:59 +0200 Subject: [PATCH 06/17] clean up --- tests/test_tools_mus.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 23c949b3a..7ad5e82b8 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -83,13 +83,13 @@ def test_wglobal(self, solver): assert len(ms) < len(cons) assert not cp.Model(ms).solve() # self.assertEqual(set(self.naive_func(cons)), set(cons[:2])) + +# add solvers that implement the native_mus method @pytest.mark.requires_solver("exact") class TestNativeMus(TestMus): def setup_method(self, solver): - # solvers that implement native mus - if solver in ["exact"]: - self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) - self.naive_func = mus_naive + self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) + self.naive_func = mus_naive class TestQuickXplain(TestMus): From 8ebbf6daba2c755704a6aba251acdd8fe2903162 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 16:54:32 +0200 Subject: [PATCH 07/17] remove assert False --- cpmpy/solvers/exact.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cpmpy/solvers/exact.py b/cpmpy/solvers/exact.py index 25ef29950..2a0401664 100644 --- a/cpmpy/solvers/exact.py +++ b/cpmpy/solvers/exact.py @@ -648,8 +648,7 @@ def get_core(self): return [self.assumption_dict[i][1] for i in self.xct_solver.getLastCore()] @classmethod - def mus_native(cls, soft, hard=[]): - assert False + def mus_native(cls, soft, hard=[]): # Create assumption variables and model with hard + (assumption -> soft) from cpmpy.tools.explain.utils import make_assump_model # avoid circular import m, soft, assumptions = make_assump_model(soft, hard) From e54a39461c38213eb64d031c1330d489577cc4d0 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 16:57:06 +0200 Subject: [PATCH 08/17] clean up --- tests/test_tools_mus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 7ad5e82b8..fab864674 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -88,7 +88,7 @@ def test_wglobal(self, solver): @pytest.mark.requires_solver("exact") class TestNativeMus(TestMus): def setup_method(self, solver): - self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) + self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive class TestQuickXplain(TestMus): From e41705ea1252510eb57d18ff3c948037a3a1d972 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 17:11:18 +0200 Subject: [PATCH 09/17] bugfix --- tests/test_tools_mus.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index fab864674..00f104943 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -87,6 +87,8 @@ def test_wglobal(self, solver): # add solvers that implement the native_mus method @pytest.mark.requires_solver("exact") class TestNativeMus(TestMus): + # use solver from conftest.py + @pytest.fixture(autouse=True) def setup_method(self, solver): self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive From 892faec6e3513021e92826670f0428571fd8fe9c Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 17:14:23 +0200 Subject: [PATCH 10/17] update --- tests/test_tools_mus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 00f104943..79588b6c6 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -89,7 +89,7 @@ def test_wglobal(self, solver): class TestNativeMus(TestMus): # use solver from conftest.py @pytest.fixture(autouse=True) - def setup_method(self, solver): + def setup_solver(self, solver): self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive From 15d7fdeaf2e5a987d6223eb0cb908f84bb3489cc Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 17:26:58 +0200 Subject: [PATCH 11/17] final bugfix --- tests/test_tools_mus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 79588b6c6..763748b83 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -34,7 +34,7 @@ def test_bug_191(self): hard = [~bv] soft = [bv] - mus_cons = self.mus_func(soft=soft, hard=hard, solver="ortools") # crashes + mus_cons = self.mus_func(soft=soft, hard=hard) # crashes assert set(mus_cons) == set(soft) mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) @@ -89,7 +89,7 @@ def test_wglobal(self, solver): class TestNativeMus(TestMus): # use solver from conftest.py @pytest.fixture(autouse=True) - def setup_solver(self, solver): + def setup_method(self, solver): self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive From d79f6c36cb16b6cfa5415b504d69db9b72c96cf2 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 17:32:34 +0200 Subject: [PATCH 12/17] update --- tests/test_tools_mus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 763748b83..502758e05 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -57,7 +57,7 @@ def test_bug_191_many_soft(self): mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_wglobal(self, solver): + def test_wglobal(self): x = cp.intvar(-9, 9, name="x") y = cp.intvar(-9, 9, name="y") From 4ba6605cc7cd0626cec478253cc397502cdd1a8f Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Fri, 24 Apr 2026 18:25:52 +0200 Subject: [PATCH 13/17] update comment --- tests/test_tools_mus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 502758e05..a734cca54 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -87,7 +87,7 @@ def test_wglobal(self): # add solvers that implement the native_mus method @pytest.mark.requires_solver("exact") class TestNativeMus(TestMus): - # use solver from conftest.py + # use modern hook to add solver argument to setup method @pytest.fixture(autouse=True) def setup_method(self, solver): self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) From 37da2820f5dadd541ff4bf36542187c4daec17d2 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Mon, 27 Apr 2026 11:45:32 +0200 Subject: [PATCH 14/17] remove autouse --- tests/test_tools_mus.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index a734cca54..8679b0657 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -4,7 +4,7 @@ from cpmpy.tools import mss_opt, marco, OCUSException from cpmpy.tools.explain import mus, mus_naive, quickxplain, quickxplain_naive, optimal_mus, optimal_mus_naive, mss, mcs, ocus, ocus_naive, mus_native - +@pytest.mark.requires_solver("ortools") class TestMus: def setup_method(self): self.mus_func = mus @@ -25,7 +25,7 @@ def test_circular(self): assert set(self.mus_func(cons)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) - def test_bug_191(self): + def test_bug_191(self, solver): """ Original Bug request: https://github.com/CPMpy/cpmpy/issues/191 When assum is a single boolvar and candidates is a list (of length 1), it fails. @@ -34,12 +34,12 @@ def test_bug_191(self): hard = [~bv] soft = [bv] - mus_cons = self.mus_func(soft=soft, hard=hard) # crashes + mus_cons = self.mus_func(soft=soft, hard=hard, solver=solver) # crashes assert set(mus_cons) == set(soft) mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_bug_191_many_soft(self): + def test_bug_191_many_soft(self, solver): """ Checking whether bugfix 191 doesn't break anything in the MUS tool chain, when the number of soft constraints > 1. @@ -52,12 +52,12 @@ def test_bug_191_many_soft(self): y == 4 ] - mus_cons = self.mus_func(soft=soft, hard=hard) # crashes + mus_cons = self.mus_func(soft=soft, hard=hard, solver=solver) # crashes assert set(mus_cons) == set(soft) mus_naive_cons = self.naive_func(soft=soft, hard=hard) # crashes assert set(mus_naive_cons) == set(soft) - def test_wglobal(self): + def test_wglobal(self, solver): x = cp.intvar(-9, 9, name="x") y = cp.intvar(-9, 9, name="y") @@ -76,7 +76,7 @@ def test_wglobal(self): # non-determinstic #self.assertEqual(set(mus(cons)), set(cons[1:3])) - ms = self.mus_func(cons) + ms = self.mus_func(cons, solver=solver) assert len(ms) < len(cons) assert not cp.Model(ms).solve() ms = self.naive_func(cons) @@ -85,12 +85,10 @@ def test_wglobal(self): # self.assertEqual(set(self.naive_func(cons)), set(cons[:2])) # add solvers that implement the native_mus method -@pytest.mark.requires_solver("exact") +@pytest.mark.requires_solver("exact", "gurobi") class TestNativeMus(TestMus): - # use modern hook to add solver argument to setup method - @pytest.fixture(autouse=True) - def setup_method(self, solver): - self.mus_func = lambda soft, hard=[], solver=solver: mus_native(soft, hard=hard, solver=solver) + def setup_method(self): + self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive class TestQuickXplain(TestMus): From 943037c4ad9b63783ca709a83219e818aa935074 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Mon, 27 Apr 2026 13:05:45 +0200 Subject: [PATCH 15/17] update --- tests/test_tools_mus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 8679b0657..7707b16f3 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -10,7 +10,7 @@ def setup_method(self): self.mus_func = mus self.naive_func = mus_naive - def test_circular(self): + def test_circular(self, solver): x = cp.intvar(0, 3, shape=4, name="x") # circular "bigger then", UNSAT cons = [ @@ -22,7 +22,7 @@ def test_circular(self): (x[3] > x[1]).implies((x[3] > x[2]) & ((x[3] == 3) | (x[1] == x[2]))) ] - assert set(self.mus_func(cons)) == set(cons[:3]) + assert set(self.mus_func(cons, solver=solver)) == set(cons[:3]) assert set(self.naive_func(cons)) == set(cons[:3]) def test_bug_191(self, solver): From 2c07e99783a2aaf844035da430f1a6638b57d6b6 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Mon, 27 Apr 2026 14:06:39 +0200 Subject: [PATCH 16/17] revert changes --- tests/test_tools_mus.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 0fc87d310..29b6bf63d 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -6,6 +6,7 @@ @pytest.mark.requires_solver("ortools") class TestMus: + @pytest.fixture def setup_method(self): self.mus_func = mus self.naive_func = mus_naive @@ -95,15 +96,13 @@ def test_decomposed_global(self, solver): assert len(set(mus_naive_cons)) == 1 # add solvers that implement the native_mus method -@pytest.mark.requires_solver("exact", "gurobi") +@pytest.mark.requires_solver("exact", "gurobi") class TestNativeMus(TestMus): def setup_method(self): self.mus_func = lambda soft, hard=[], solver="exact": mus_native(soft, hard=hard, solver=solver) self.naive_func = mus_naive - - class TestQuickXplain(TestMus): def setup_method(self): From b28278d3a7d83fa3b212142d1fa40e408cf37b64 Mon Sep 17 00:00:00 2001 From: OrestisLomis Date: Mon, 27 Apr 2026 14:08:17 +0200 Subject: [PATCH 17/17] update --- tests/test_tools_mus.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tools_mus.py b/tests/test_tools_mus.py index 29b6bf63d..cb18ed9d1 100644 --- a/tests/test_tools_mus.py +++ b/tests/test_tools_mus.py @@ -6,7 +6,6 @@ @pytest.mark.requires_solver("ortools") class TestMus: - @pytest.fixture def setup_method(self): self.mus_func = mus self.naive_func = mus_naive