From 34069e777902c4aa7062e4b3a0b944ada082f0ab Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 13 Oct 2026 15:50:57 +0100 Subject: [PATCH 1/5] Add getCountedSols() to retrieve solutions collected by count() --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 7 ++++ src/pyscipopt/scip.pxi | 79 ++++++++++++++++++++++++++++++++++++++++++ src/pyscipopt/scip.pyi | 1 + tests/test_model.py | 34 ++++++++++++++++++ 5 files changed, 122 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c19ccfa4f..70b43499a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- Added `getCountedSols()` to retrieve the solutions collected by `count()` (#906) ### Fixed ### Changed ### Removed diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index fe5f3ac6b..88d8b017c 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -522,6 +522,9 @@ cdef extern from "scip/scip.h": ctypedef struct SCIP_DIVESET: pass + ctypedef struct SCIP_SPARSESOL: + pass + ctypedef struct SCIP_HASHMAP: pass @@ -824,6 +827,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPreleaseVar(SCIP* scip, SCIP_VAR** var) SCIP_RETCODE SCIPtransformVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar) SCIP_RETCODE SCIPgetTransformedVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar) + SCIP_RETCODE SCIPgetProbvarLinearSum(SCIP* scip, SCIP_VAR** vars, SCIP_Real* scalars, int* nvars, int varssize, SCIP_Real* constant, int* requiredsize) SCIP_RETCODE SCIPaddVarLocks(SCIP* scip, SCIP_VAR* var, int nlocksdown, int nlocksup) SCIP_RETCODE SCIPaddVarLocksType(SCIP* scip, SCIP_VAR* var, SCIP_LOCKTYPE locktype, int nlocksdown, int nlocksup) int SCIPvarGetNLocksDown(SCIP_VAR* var) @@ -2102,6 +2106,9 @@ cdef extern from "scip/cons_countsols.h": SCIP_RETCODE SCIPcount(SCIP* scip) SCIP_RETCODE SCIPsetParamsCountsols(SCIP* scip) SCIP_Longint SCIPgetNCountedSols(SCIP* scip, SCIP_Bool* valid) + void SCIPgetCountedSparseSols(SCIP* scip, SCIP_VAR*** vars, int* nvars, SCIP_SPARSESOL*** sols, int* nsols) + void SCIPsparseSolGetFirstSol(SCIP_SPARSESOL* sparsesol, SCIP_Longint* sol, int nvars) + SCIP_Bool SCIPsparseSolGetNextSol(SCIP_SPARSESOL* sparsesol, SCIP_Longint* sol, int nvars) cdef extern from "scip/paramset.h": diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 52123c2aa..2c9596e92 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -12545,6 +12545,85 @@ cdef class Model: print('total number of solutions found is not valid!') return nsols + def getCountedSols(self): + """ + Get the feasible solutions collected by `count()`. + + Solutions are only collected if the parameter `constraints/countsols/collect` + is set to True before calling `count()`. Only integral variables are counted, + so continuous variables are not part of the returned solutions. All solutions + are expanded, so their number can be huge. The solutions are freed together + with the transformed problem. + + Returns + ------- + list of dict + Each solution maps the names of the integral original variables to their values. + + """ + cdef SCIP_VAR** activevars + cdef SCIP_SPARSESOL** sparsesols + cdef SCIP_VAR** vars + cdef SCIP_Real* scalars + cdef SCIP_Longint* sol + cdef SCIP_VAR* transvar + cdef SCIP_Real constant + cdef SCIP_Bool valid + cdef int nactivevars + cdef int nsparsesols + cdef int nvars + cdef int requiredsize + cdef int i + cdef int s + + if SCIPgetStage(self._scip) < SCIP_STAGE_TRANSFORMED or SCIPgetStage(self._scip) == SCIP_STAGE_FREETRANS: + raise Warning("method cannot be called before count() or after the transformed problem is freed") + + nsols = SCIPgetNCountedSols(self._scip, &valid) + if not valid: + raise Warning("number of counted solutions does not fit into a long integer, cannot collect them") + + SCIPgetCountedSparseSols(self._scip, &activevars, &nactivevars, &sparsesols, &nsparsesols) + if nsparsesols == 0: + if nsols > 0: + raise Warning("no solutions were collected, set constraints/countsols/collect to True before calling count()") + return [] + + activeidx = {activevars[i]: i for i in range(nactivevars)} + + vars = malloc((nactivevars + 1) * sizeof(SCIP_VAR*)) + scalars = malloc((nactivevars + 1) * sizeof(SCIP_Real)) + sol = malloc((nactivevars + 1) * sizeof(SCIP_Longint)) + + try: + origvars = [] + for i in range(SCIPgetNOrigVars(self._scip)): + if not SCIPvarIsIntegral(SCIPgetOrigVars(self._scip)[i]): + continue + PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, SCIPgetOrigVars(self._scip)[i], &transvar)) + vars[0] = transvar + scalars[0] = 1.0 + nvars = 1 + constant = 0.0 + PY_SCIP_CALL(SCIPgetProbvarLinearSum(self._scip, vars, scalars, &nvars, nactivevars + 1, &constant, &requiredsize)) + terms = [(activeidx[vars[j]], scalars[j]) for j in range(nvars)] + origvars.append((bytes(SCIPvarGetName(SCIPgetOrigVars(self._scip)[i])).decode("utf-8"), constant, terms)) + + sols = [] + for s in range(nsparsesols): + SCIPsparseSolGetFirstSol(sparsesols[s], sol, nactivevars) + while True: + sols.append({name: c + sum(scalar * sol[idx] for idx, scalar in terms) + for name, c, terms in origvars}) + if not SCIPsparseSolGetNextSol(sparsesols[s], sol, nactivevars): + break + finally: + free(vars) + free(scalars) + free(sol) + + return sols + def setParamsCountsols(self): """Sets SCIP parameters such that a valid counting process is possible.""" PY_SCIP_CALL(SCIPsetParamsCountsols(self._scip)) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 316b13e9f..09ba93256 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1226,6 +1226,7 @@ class Model: def getConsVars(self, constraint: Constraint) -> list[Variable] | None: ... def getConss(self, transformed: bool = True) -> list[Constraint]: ... def getFocusNode(self) -> Node | None: ... + def getCountedSols(self) -> list[dict[str, float]]: ... def getCurrentNode(self) -> Node | None: ... def getCutEfficacy(self, cut: Row, sol: Solution | None = None) -> float: ... def getCutLPSolCutoffDistance(self, cut: Row, sol: Solution) -> float: ... diff --git a/tests/test_model.py b/tests/test_model.py index 6736fbb5f..db012bae9 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -662,3 +662,37 @@ def test_memory_methods(): assert m.getMemUsed() > 0 assert m.getMemTotal() > 0 + + +def test_getCountedSols(): + m = Model() + m.hideOutput() + m.setParam("constraints/countsols/collect", True) + x = m.addVar("x", vtype="I", lb=-2, ub=3) + y = m.addVar("y", vtype="I", lb=0, ub=4) + z = m.addVar("z", vtype="I", lb=0, ub=10) + w = m.addVar("w", vtype="B") + m.addVar("c", lb=0, ub=1) + m.addCons(z == 2*x + 3) + m.addCons(x + y <= 4) + m.addCons(w + y >= 1) + + with pytest.raises(Warning): + m.getCountedSols() + + m.count() + sols = m.getCountedSols() + expected = {(a, b, c, d) for a, b, c, d in itertools.product(range(-2, 4), range(5), range(11), range(2)) + if c == 2*a + 3 and a + b <= 4 and d + b >= 1} + assert len(sols) == m.getNCountedSols() == len(expected) + assert all(set(sol) == {"x", "y", "z", "w"} for sol in sols) + assert {tuple(round(sol[k]) for k in "xyzw") for sol in sols} == expected + + m.freeTransform() + with pytest.raises(Warning): + m.getCountedSols() + + m.setParam("constraints/countsols/collect", False) + m.count() + with pytest.raises(Warning): + m.getCountedSols() From 1001e92cc42dd3a8328f74c1cc7341d3c19a68c1 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 13 Oct 2026 15:57:22 +0100 Subject: [PATCH 2/5] Drop try/finally in getCountedSols() --- src/pyscipopt/scip.pxi | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 2c9596e92..274787415 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -12593,34 +12593,34 @@ cdef class Model: vars = malloc((nactivevars + 1) * sizeof(SCIP_VAR*)) scalars = malloc((nactivevars + 1) * sizeof(SCIP_Real)) - sol = malloc((nactivevars + 1) * sizeof(SCIP_Longint)) - try: - origvars = [] - for i in range(SCIPgetNOrigVars(self._scip)): - if not SCIPvarIsIntegral(SCIPgetOrigVars(self._scip)[i]): - continue - PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, SCIPgetOrigVars(self._scip)[i], &transvar)) - vars[0] = transvar - scalars[0] = 1.0 - nvars = 1 - constant = 0.0 - PY_SCIP_CALL(SCIPgetProbvarLinearSum(self._scip, vars, scalars, &nvars, nactivevars + 1, &constant, &requiredsize)) - terms = [(activeidx[vars[j]], scalars[j]) for j in range(nvars)] - origvars.append((bytes(SCIPvarGetName(SCIPgetOrigVars(self._scip)[i])).decode("utf-8"), constant, terms)) - - sols = [] - for s in range(nsparsesols): - SCIPsparseSolGetFirstSol(sparsesols[s], sol, nactivevars) - while True: - sols.append({name: c + sum(scalar * sol[idx] for idx, scalar in terms) - for name, c, terms in origvars}) - if not SCIPsparseSolGetNextSol(sparsesols[s], sol, nactivevars): - break - finally: - free(vars) - free(scalars) - free(sol) + origvars = [] + for i in range(SCIPgetNOrigVars(self._scip)): + if not SCIPvarIsIntegral(SCIPgetOrigVars(self._scip)[i]): + continue + PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, SCIPgetOrigVars(self._scip)[i], &transvar)) + vars[0] = transvar + scalars[0] = 1.0 + nvars = 1 + constant = 0.0 + PY_SCIP_CALL(SCIPgetProbvarLinearSum(self._scip, vars, scalars, &nvars, nactivevars + 1, &constant, &requiredsize)) + terms = [(activeidx[vars[j]], scalars[j]) for j in range(nvars)] + origvars.append((bytes(SCIPvarGetName(SCIPgetOrigVars(self._scip)[i])).decode("utf-8"), constant, terms)) + + free(vars) + free(scalars) + + sol = malloc(nactivevars * sizeof(SCIP_Longint)) + sols = [] + for s in range(nsparsesols): + SCIPsparseSolGetFirstSol(sparsesols[s], sol, nactivevars) + while True: + sols.append({name: c + sum(scalar * sol[idx] for idx, scalar in terms) + for name, c, terms in origvars}) + if not SCIPsparseSolGetNextSol(sparsesols[s], sol, nactivevars): + break + + free(sol) return sols From d5a9a7b4da2e399abff9fd04214f44fdf3873b6b Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 13 Oct 2026 16:24:33 +0100 Subject: [PATCH 3/5] Check collect param instead of stage in getCountedSols() --- src/pyscipopt/scip.pxi | 13 +++++-------- tests/test_model.py | 6 ++---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 274787415..023cb3cbb 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -12551,9 +12551,8 @@ cdef class Model: Solutions are only collected if the parameter `constraints/countsols/collect` is set to True before calling `count()`. Only integral variables are counted, - so continuous variables are not part of the returned solutions. All solutions - are expanded, so their number can be huge. The solutions are freed together - with the transformed problem. + so continuous variables are not part of the returned solutions. The solutions + are freed together with the transformed problem. Returns ------- @@ -12576,17 +12575,15 @@ cdef class Model: cdef int i cdef int s - if SCIPgetStage(self._scip) < SCIP_STAGE_TRANSFORMED or SCIPgetStage(self._scip) == SCIP_STAGE_FREETRANS: - raise Warning("method cannot be called before count() or after the transformed problem is freed") + if not self.getParam("constraints/countsols/collect"): + raise Warning("set constraints/countsols/collect to True before calling count()") - nsols = SCIPgetNCountedSols(self._scip, &valid) + SCIPgetNCountedSols(self._scip, &valid) if not valid: raise Warning("number of counted solutions does not fit into a long integer, cannot collect them") SCIPgetCountedSparseSols(self._scip, &activevars, &nactivevars, &sparsesols, &nsparsesols) if nsparsesols == 0: - if nsols > 0: - raise Warning("no solutions were collected, set constraints/countsols/collect to True before calling count()") return [] activeidx = {activevars[i]: i for i in range(nactivevars)} diff --git a/tests/test_model.py b/tests/test_model.py index db012bae9..8cc6a98ac 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -677,8 +677,7 @@ def test_getCountedSols(): m.addCons(x + y <= 4) m.addCons(w + y >= 1) - with pytest.raises(Warning): - m.getCountedSols() + assert m.getCountedSols() == [] m.count() sols = m.getCountedSols() @@ -689,8 +688,7 @@ def test_getCountedSols(): assert {tuple(round(sol[k]) for k in "xyzw") for sol in sols} == expected m.freeTransform() - with pytest.raises(Warning): - m.getCountedSols() + assert m.getCountedSols() == [] m.setParam("constraints/countsols/collect", False) m.count() From 161e1aedbbc077800ea39e6f3c6fb65dd314d69f Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 13 Oct 2026 16:46:02 +0100 Subject: [PATCH 4/5] Return counted solutions over transformed variables --- src/pyscipopt/scip.pxd | 1 - src/pyscipopt/scip.pxi | 54 ++++++++++-------------------------------- src/pyscipopt/scip.pyi | 2 +- tests/test_model.py | 5 ++-- 4 files changed, 17 insertions(+), 45 deletions(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 88d8b017c..bf5479552 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -827,7 +827,6 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPreleaseVar(SCIP* scip, SCIP_VAR** var) SCIP_RETCODE SCIPtransformVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar) SCIP_RETCODE SCIPgetTransformedVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar) - SCIP_RETCODE SCIPgetProbvarLinearSum(SCIP* scip, SCIP_VAR** vars, SCIP_Real* scalars, int* nvars, int varssize, SCIP_Real* constant, int* requiredsize) SCIP_RETCODE SCIPaddVarLocks(SCIP* scip, SCIP_VAR* var, int nlocksdown, int nlocksup) SCIP_RETCODE SCIPaddVarLocksType(SCIP* scip, SCIP_VAR* var, SCIP_LOCKTYPE locktype, int nlocksdown, int nlocksup) int SCIPvarGetNLocksDown(SCIP_VAR* var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 023cb3cbb..bb2df172e 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -12550,28 +12550,23 @@ cdef class Model: Get the feasible solutions collected by `count()`. Solutions are only collected if the parameter `constraints/countsols/collect` - is set to True before calling `count()`. Only integral variables are counted, - so continuous variables are not part of the returned solutions. The solutions - are freed together with the transformed problem. + is set to True before calling `count()`. They are given over the transformed + integral variables, so continuous variables and variables removed by presolving + are not part of them; turn presolving off to get every integral variable. + The solutions are freed together with the transformed problem. Returns ------- list of dict - Each solution maps the names of the integral original variables to their values. + Each solution maps the names of the transformed variables to their values. """ - cdef SCIP_VAR** activevars - cdef SCIP_SPARSESOL** sparsesols cdef SCIP_VAR** vars - cdef SCIP_Real* scalars + cdef SCIP_SPARSESOL** sparsesols cdef SCIP_Longint* sol - cdef SCIP_VAR* transvar - cdef SCIP_Real constant cdef SCIP_Bool valid - cdef int nactivevars - cdef int nsparsesols cdef int nvars - cdef int requiredsize + cdef int nsparsesols cdef int i cdef int s @@ -12582,39 +12577,16 @@ cdef class Model: if not valid: raise Warning("number of counted solutions does not fit into a long integer, cannot collect them") - SCIPgetCountedSparseSols(self._scip, &activevars, &nactivevars, &sparsesols, &nsparsesols) - if nsparsesols == 0: - return [] - - activeidx = {activevars[i]: i for i in range(nactivevars)} - - vars = malloc((nactivevars + 1) * sizeof(SCIP_VAR*)) - scalars = malloc((nactivevars + 1) * sizeof(SCIP_Real)) - - origvars = [] - for i in range(SCIPgetNOrigVars(self._scip)): - if not SCIPvarIsIntegral(SCIPgetOrigVars(self._scip)[i]): - continue - PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, SCIPgetOrigVars(self._scip)[i], &transvar)) - vars[0] = transvar - scalars[0] = 1.0 - nvars = 1 - constant = 0.0 - PY_SCIP_CALL(SCIPgetProbvarLinearSum(self._scip, vars, scalars, &nvars, nactivevars + 1, &constant, &requiredsize)) - terms = [(activeidx[vars[j]], scalars[j]) for j in range(nvars)] - origvars.append((bytes(SCIPvarGetName(SCIPgetOrigVars(self._scip)[i])).decode("utf-8"), constant, terms)) - - free(vars) - free(scalars) + SCIPgetCountedSparseSols(self._scip, &vars, &nvars, &sparsesols, &nsparsesols) + names = [bytes(SCIPvarGetName(vars[i])).decode("utf-8") for i in range(nvars)] - sol = malloc(nactivevars * sizeof(SCIP_Longint)) + sol = malloc(nvars * sizeof(SCIP_Longint)) sols = [] for s in range(nsparsesols): - SCIPsparseSolGetFirstSol(sparsesols[s], sol, nactivevars) + SCIPsparseSolGetFirstSol(sparsesols[s], sol, nvars) while True: - sols.append({name: c + sum(scalar * sol[idx] for idx, scalar in terms) - for name, c, terms in origvars}) - if not SCIPsparseSolGetNextSol(sparsesols[s], sol, nactivevars): + sols.append({names[i]: sol[i] for i in range(nvars)}) + if not SCIPsparseSolGetNextSol(sparsesols[s], sol, nvars): break free(sol) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 09ba93256..1cc29b1a4 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1226,7 +1226,7 @@ class Model: def getConsVars(self, constraint: Constraint) -> list[Variable] | None: ... def getConss(self, transformed: bool = True) -> list[Constraint]: ... def getFocusNode(self) -> Node | None: ... - def getCountedSols(self) -> list[dict[str, float]]: ... + def getCountedSols(self) -> list[dict[str, int]]: ... def getCurrentNode(self) -> Node | None: ... def getCutEfficacy(self, cut: Row, sol: Solution | None = None) -> float: ... def getCutLPSolCutoffDistance(self, cut: Row, sol: Solution) -> float: ... diff --git a/tests/test_model.py b/tests/test_model.py index 8cc6a98ac..347171f39 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -668,6 +668,7 @@ def test_getCountedSols(): m = Model() m.hideOutput() m.setParam("constraints/countsols/collect", True) + m.setPresolve(SCIP_PARAMSETTING.OFF) x = m.addVar("x", vtype="I", lb=-2, ub=3) y = m.addVar("y", vtype="I", lb=0, ub=4) z = m.addVar("z", vtype="I", lb=0, ub=10) @@ -684,8 +685,8 @@ def test_getCountedSols(): expected = {(a, b, c, d) for a, b, c, d in itertools.product(range(-2, 4), range(5), range(11), range(2)) if c == 2*a + 3 and a + b <= 4 and d + b >= 1} assert len(sols) == m.getNCountedSols() == len(expected) - assert all(set(sol) == {"x", "y", "z", "w"} for sol in sols) - assert {tuple(round(sol[k]) for k in "xyzw") for sol in sols} == expected + assert all(set(sol) == {"t_x", "t_y", "t_z", "t_w"} for sol in sols) + assert {tuple(sol["t_" + k] for k in "xyzw") for sol in sols} == expected m.freeTransform() assert m.getCountedSols() == [] From ea1dacf484459d3f7283040da01ddce4f42c8676 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 13 Oct 2026 16:48:40 +0100 Subject: [PATCH 5/5] Require presolving off in getCountedSols() --- src/pyscipopt/scip.pxi | 11 ++++++----- tests/test_model.py | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index bb2df172e..f7b449022 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -12549,11 +12549,10 @@ cdef class Model: """ Get the feasible solutions collected by `count()`. - Solutions are only collected if the parameter `constraints/countsols/collect` - is set to True before calling `count()`. They are given over the transformed - integral variables, so continuous variables and variables removed by presolving - are not part of them; turn presolving off to get every integral variable. - The solutions are freed together with the transformed problem. + Requires `constraints/countsols/collect` to be True and presolving to be off + before calling `count()`. Only integral variables are counted, so continuous + variables are not part of the solutions. The solutions are freed together with + the transformed problem. Returns ------- @@ -12572,6 +12571,8 @@ cdef class Model: if not self.getParam("constraints/countsols/collect"): raise Warning("set constraints/countsols/collect to True before calling count()") + if self.getParam("presolving/maxrounds") != 0: + raise Warning("turn presolving off before calling count()") SCIPgetNCountedSols(self._scip, &valid) if not valid: diff --git a/tests/test_model.py b/tests/test_model.py index 347171f39..c4ee90b0e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -692,6 +692,10 @@ def test_getCountedSols(): assert m.getCountedSols() == [] m.setParam("constraints/countsols/collect", False) - m.count() + with pytest.raises(Warning): + m.getCountedSols() + + m.setParam("constraints/countsols/collect", True) + m.setPresolve(SCIP_PARAMSETTING.DEFAULT) with pytest.raises(Warning): m.getCountedSols()