From 414b859974410fff314214c18b7e929146c71bb2 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Mon, 10 Nov 2025 20:46:25 +0100 Subject: [PATCH 1/9] add function to get variable status --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 11 +++++++++++ src/pyscipopt/scip.pxi | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index da9fc99fa..2dc3847fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - MatrixVariable comparisons (<=, >=, ==) now support numpy's broadcast feature. - Added methods: getMaxDepth(), getPlungeDepth(), getLowerbound(), getCutoffbound(), getNNodeLPIterations(), getNStrongbranchLPIterations(). - setup.py now automatically detects conda environments when SCIPOPTDIR is not defined. +- add function vstatus to get variable status in variable class ### Fixed - Implemented all binary operations between MatrixExpr and GenExpr - Fixed the type of @ matrix operation result from MatrixVariable to MatrixExpr. diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 3f6b5ce15..b182a8da2 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -30,6 +30,16 @@ cdef extern from "scip/scip.h": SCIP_VARTYPE SCIP_VARTYPE_IMPLINT SCIP_VARTYPE SCIP_VARTYPE_CONTINUOUS + ctypedef int SCIP_VARSTATUS + cdef extern from "scip/type_var.h": + SCIP_VARSTATUS SCIP_VARSTATUS_ORIGINAL + SCIP_VARSTATUS SCIP_VARSTATUS_LOOSE + SCIP_VARSTATUS SCIP_VARSTATUS_COLUMN + SCIP_VARSTATUS SCIP_VARSTATUS_FIXED + SCIP_VARSTATUS SCIP_VARSTATUS_AGGREGATED + SCIP_VARSTATUS SCIP_VARSTATUS_MULTAGGR + SCIP_VARSTATUS SCIP_VARSTATUS_NEGATED + ctypedef int SCIP_OBJSENSE cdef extern from "scip/type_prob.h": SCIP_OBJSENSE SCIP_OBJSENSE_MAXIMIZE @@ -790,6 +800,7 @@ cdef extern from "scip/scip.h": int SCIPgetNImplVars(SCIP* scip) int SCIPgetNContVars(SCIP* scip) SCIP_VARTYPE SCIPvarGetType(SCIP_VAR* var) + SCIP_VARSTATUS SCIPvarGetStatus(SCIP_VAR* var) SCIP_Bool SCIPvarIsOriginal(SCIP_VAR* var) SCIP_Bool SCIPvarIsTransformed(SCIP_VAR* var) SCIP_COL* SCIPvarGetCol(SCIP_VAR* var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 72dc85d2b..61f887f35 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1538,6 +1538,32 @@ cdef class Variable(Expr): elif vartype == SCIP_VARTYPE_IMPLINT: return "IMPLINT" + def vstatus(self): + """ + Retrieve the variable status (ORIGINAL, LOOSE, COLUMN, FIXED, AGGREGATED, MULTAGGR, NEGATED) + + Returns + ------- + str + "ORIGINAL", "LOOSE", "COLUMN", "FIXED", "AGGREGATED", "MULTAGGR", "NEGATED" + + """ + varstatus = SCIPvarGetStatus(self.scip_var) + if varstatus == SCIP_VARSTATUS_ORIGINAL: + return "ORIGINAL" + elif varstatus == SCIP_VARSTATUS_LOOSE: + return "LOOSE" + elif varstatus == SCIP_VARSTATUS_COLUMN: + return "COLUMN" + elif varstatus == SCIP_VARSTATUS_FIXED: + return "FIXED" + elif varstatus == SCIP_VARSTATUS_AGGREGATED: + return "AGGREGATED" + elif varstatus == SCIP_VARSTATUS_MULTAGGR: + return "MULTAGGR" + elif varstatus == SCIP_VARSTATUS_NEGATED: + return "NEGATED" + def isOriginal(self): """ Retrieve whether the variable belongs to the original problem From 31bb713ad31cf7a561fb7a9b6a52b94dfd89c598 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Mon, 10 Nov 2025 20:49:37 +0100 Subject: [PATCH 2/9] add isActive to get whether variable is active --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dc3847fb..edbfc4f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added methods: getMaxDepth(), getPlungeDepth(), getLowerbound(), getCutoffbound(), getNNodeLPIterations(), getNStrongbranchLPIterations(). - setup.py now automatically detects conda environments when SCIPOPTDIR is not defined. - add function vstatus to get variable status in variable class +- add function isActive to get whether a variable is active in variable class ### Fixed - Implemented all binary operations between MatrixExpr and GenExpr - Fixed the type of @ matrix operation result from MatrixVariable to MatrixExpr. diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index b182a8da2..48b34d93a 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -803,6 +803,7 @@ cdef extern from "scip/scip.h": SCIP_VARSTATUS SCIPvarGetStatus(SCIP_VAR* var) SCIP_Bool SCIPvarIsOriginal(SCIP_VAR* var) SCIP_Bool SCIPvarIsTransformed(SCIP_VAR* var) + SCIP_Bool SCIPvarIsActive(SCIP_VAR* var) SCIP_COL* SCIPvarGetCol(SCIP_VAR* var) SCIP_Bool SCIPvarIsInLP(SCIP_VAR* var) SCIP_Real SCIPvarGetLbOriginal(SCIP_VAR* var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 61f887f35..7490b3888 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1575,6 +1575,17 @@ cdef class Variable(Expr): """ return SCIPvarIsOriginal(self.scip_var) + def isActive(self): + """ + Retrieve whether the variable is active + + Returns + ------- + bool + + """ + return SCIPvarIsActive(self.scip_var) + def isInLP(self): """ Retrieve whether the variable is a COLUMN variable that is member of the current LP. From 21ee44f0ddc86ce334aa6314defad11a857c9367 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Mon, 10 Nov 2025 20:56:24 +0100 Subject: [PATCH 3/9] add markDoNotMultaggrVar to mark a variable --- CHANGELOG.md | 1 + src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index edbfc4f64..6bd2b4a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - setup.py now automatically detects conda environments when SCIPOPTDIR is not defined. - add function vstatus to get variable status in variable class - add function isActive to get whether a variable is active in variable class +- add function markDoNotMultaggrVar to mark a variable to not allowed to be multi-aggregated ### Fixed - Implemented all binary operations between MatrixExpr and GenExpr - Fixed the type of @ matrix operation result from MatrixVariable to MatrixExpr. diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 48b34d93a..da4a3cfb6 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -778,6 +778,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPdelVar(SCIP* scip, SCIP_VAR* var, SCIP_Bool* deleted) SCIP_RETCODE SCIPchgVarType(SCIP* scip, SCIP_VAR* var, SCIP_VARTYPE vartype, SCIP_Bool* infeasible) + SCIP_RETCODE SCIPmarkDoNotMultaggrVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPcaptureVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPaddPricedVar(SCIP* scip, SCIP_VAR* var, SCIP_Real score) SCIP_RETCODE SCIPreleaseVar(SCIP* scip, SCIP_VAR** var) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 7490b3888..5c7dff515 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -4450,6 +4450,17 @@ cdef class Model: if infeasible: print('could not change variable type of variable %s' % var) + def markDoNotMultaggrVar(self, Variable var): + """ + Marks a variable to not allowed to be multi-aggregated. + + Parameters + ---------- + var : Variable + variable to mark + """ + PY_SCIP_CALL(SCIPmarkDoNotMultaggrVar(self._scip, var.scip_var)) + def getVars(self, transformed=False): """ Retrieve all variables. From f9c5321c4190ca797c20bcad2e2f2c75879aaad5 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Tue, 11 Nov 2025 10:37:51 +0100 Subject: [PATCH 4/9] Update src/pyscipopt/scip.pxi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com> --- src/pyscipopt/scip.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 5c7dff515..933db831b 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -4452,7 +4452,7 @@ cdef class Model: def markDoNotMultaggrVar(self, Variable var): """ - Marks a variable to not allowed to be multi-aggregated. + Marks a variable preventing it from being multi-aggregated. Parameters ---------- From c214e9f466f1f2ed8cf8eb967c8a54fa96156b08 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Tue, 11 Nov 2025 10:39:22 +0100 Subject: [PATCH 5/9] Update src/pyscipopt/scip.pxi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Dionísio <57299939+Joao-Dionisio@users.noreply.github.com> --- src/pyscipopt/scip.pxi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 933db831b..9641fe089 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1538,7 +1538,7 @@ cdef class Variable(Expr): elif vartype == SCIP_VARTYPE_IMPLINT: return "IMPLINT" - def vstatus(self): + def getStatus(self): """ Retrieve the variable status (ORIGINAL, LOOSE, COLUMN, FIXED, AGGREGATED, MULTAGGR, NEGATED) From 35d33152632082177cba3ec74375633d84260ff1 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 11 Nov 2025 09:59:24 +0000 Subject: [PATCH 6/9] Add markDoNotAggrVar and minor readme changes --- CHANGELOG.md | 7 ++++--- src/pyscipopt/scip.pxd | 1 + src/pyscipopt/scip.pxi | 11 +++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd2b4a66..ab841d2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ - MatrixVariable comparisons (<=, >=, ==) now support numpy's broadcast feature. - Added methods: getMaxDepth(), getPlungeDepth(), getLowerbound(), getCutoffbound(), getNNodeLPIterations(), getNStrongbranchLPIterations(). - setup.py now automatically detects conda environments when SCIPOPTDIR is not defined. -- add function vstatus to get variable status in variable class -- add function isActive to get whether a variable is active in variable class -- add function markDoNotMultaggrVar to mark a variable to not allowed to be multi-aggregated +- Added function getStatus() to get variable status in variable class +- Added function isActive() to get whether a variable is active in variable class +- Added function markDoNotAggrVar() to prevent a variable from being aggregated +- Added function markDoNotMultaggrVar() to prevent a variable from being multi-aggregated ### Fixed - Implemented all binary operations between MatrixExpr and GenExpr - Fixed the type of @ matrix operation result from MatrixVariable to MatrixExpr. diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index da4a3cfb6..7d99e4715 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -778,6 +778,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPdelVar(SCIP* scip, SCIP_VAR* var, SCIP_Bool* deleted) SCIP_RETCODE SCIPchgVarType(SCIP* scip, SCIP_VAR* var, SCIP_VARTYPE vartype, SCIP_Bool* infeasible) + SCIP_RETCODE SCIPmarkDoNotAggrVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPmarkDoNotMultaggrVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPcaptureVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPaddPricedVar(SCIP* scip, SCIP_VAR* var, SCIP_Real score) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 9641fe089..1781bc1d3 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -4450,6 +4450,17 @@ cdef class Model: if infeasible: print('could not change variable type of variable %s' % var) + def markDoNotAggrVar(self, Variable var): + """ + Marks a variable preventing it from being aggregated. + + Parameters + ---------- + var : Variable + variable to mark + """ + PY_SCIP_CALL(SCIPmarkDoNotAggrVar(self._scip, var.scip_var)) + def markDoNotMultaggrVar(self, Variable var): """ Marks a variable preventing it from being multi-aggregated. From ff58309fb69e77495476d54ca40d46225139f017 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 11 Nov 2025 10:10:50 +0000 Subject: [PATCH 7/9] formatting in scip.pxi --- src/pyscipopt/scip.pxi | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 1781bc1d3..5f2d864c5 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -1120,7 +1120,6 @@ cdef class Solution: PY_SCIP_CALL(SCIPtranslateSubSol(target._scip, self.scip, self.sol, NULL, source_vars, &(targetSol.sol))) return targetSol - cdef class BoundChange: """Bound change.""" @@ -1597,7 +1596,6 @@ cdef class Variable(Expr): """ return SCIPvarIsInLP(self.scip_var) - def getIndex(self): """ Retrieve the unique index of the variable. @@ -2080,7 +2078,6 @@ class MatrixVariable(MatrixExpr): mayround[idx] = self[idx].varMayRound() return mayround - cdef class Constraint: """Base class holding a pointer to corresponding SCIP_CONS""" @@ -2621,7 +2618,6 @@ cdef class Model: else: PY_SCIP_CALL(SCIPcopy(sourceModel._scip, self._scip, NULL, NULL, n, globalcopy, enablepricing, threadsafe, True, self._valid)) - def attachEventHandlerCallback(self, callback, events, @@ -2671,7 +2667,6 @@ cdef class Model: self.includeEventhdlr(event_handler, name, description) - def __dealloc__(self): # call C function directly, because we can no longer call this object's methods, according to # http://docs.cython.org/src/reference/extension_types.html#finalization-dealloc @@ -2854,7 +2849,6 @@ cdef class Model: """ return SCIPminorVersion() - def getTechVersion(self): """ Retrieve SCIP technical version. @@ -3596,7 +3590,6 @@ cdef class Model: return iters # Objective function - def setMinimize(self): """Set the objective sense to minimization.""" PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MINIMIZE)) @@ -3942,7 +3935,6 @@ cdef class Model: locale.setlocale(locale.LC_NUMERIC,user_locale) # Variable Functions - def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar=False, pricedVarScore=1.0, deletable=False): """ Create a new variable. Default variable is non-negative and continuous. @@ -4422,7 +4414,6 @@ cdef class Model: ub = SCIPinfinity(self._scip) PY_SCIP_CALL(SCIPchgVarUbNode(self._scip, node.scip_node, var.scip_var, ub)) - def chgVarType(self, Variable var, vtype): """ Changes the type of a variable. @@ -4813,7 +4804,6 @@ cdef class Model: """Marks the given node to be propagated again the next time a node of its subtree is processed.""" PY_SCIP_CALL(SCIPrepropagateNode(self._scip, node.scip_node)) - # LP Methods def getLPSolstat(self): """ @@ -4826,7 +4816,6 @@ cdef class Model: """ return SCIPgetLPSolstat(self._scip) - def constructLP(self): """ Makes sure that the LP of the current node is loaded and @@ -7103,7 +7092,6 @@ cdef class Model: return pyCons - def addMatrixConsIndicator(self, cons: Union[ExprCons, MatrixExprCons], binvar: Union[Variable, MatrixVariable] = None, activeone: Union[bool, np.ndarray] = True, name: Union[str, np.ndarray] = "", initial: Union[bool, np.ndarray] = True, separate: Union[bool, np.ndarray] = True, @@ -7686,7 +7674,6 @@ cdef class Model: return activity - def getSlack(self, Constraint cons, Solution sol = None, side = None): """ Retrieve slack of given constraint. @@ -8707,7 +8694,6 @@ cdef class Model: """ PY_SCIP_CALL( SCIPincludeBendersDefaultCuts(self._scip, benders._benders) ) - def includeEventhdlr(self, Eventhdlr eventhdlr, name, desc): """ Include an event handler. @@ -9344,7 +9330,6 @@ cdef class Model: # TODO: It might be necessary in increment the reference to benders i.e Py_INCREF(benders) Py_INCREF(benderscut) - def getLPBranchCands(self): """ Gets branching candidates for LP solution branching (fractional variables) along with solution values, @@ -9388,9 +9373,9 @@ cdef class Model: """ Gets number of branching candidates for LP solution branching (number of fractional variables) - Returns - ------- - int + Returns + ------- + int number of LP branching candidates """ @@ -9447,7 +9432,6 @@ cdef class Model: PY_SCIP_CALL(SCIPbranchVar(self._scip, wrapper.ptr[0], &downchild, &eqchild, &upchild)) return Node.create(downchild), Node.create(eqchild), Node.create(upchild) - def branchVarVal(self, Variable variable, value): """ Branches on variable using a value which separates the domain of the variable. @@ -11051,7 +11035,6 @@ cdef class Model: v = str_conversion(value) PY_SCIP_CALL(SCIPsetStringParam(self._scip, n, v)) - def getParam(self, name): """ Get the value of a parameter of type @@ -11541,7 +11524,6 @@ cdef class Model: """ return SCIPgetTreesizeEstimation(self._scip) - def getBipartiteGraphRepresentation(self, prev_col_features=None, prev_edge_features=None, prev_row_features=None, static_only=False, suppress_warnings=False): """ From ab8122b3f7de8a5780bb76301e8be37d9c19fd02 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Tue, 11 Nov 2025 10:51:43 +0000 Subject: [PATCH 8/9] add test for variable marking --- tests/test_vars.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_vars.py b/tests/test_vars.py index 604c7870b..7f02f142d 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -111,3 +111,32 @@ def test_getNBranchingsCurrentRun(): n_branchings += var.getNBranchingsCurrentRun(SCIP_BRANCHDIR.DOWNWARDS) assert n_branchings == m.getNNodes() - 1 + +def test_markDoNotAggrVar(): + model = Model() + x = model.addVar("x", obj=2, lb=0, ub=10) + y = model.addVar("y", obj=3, lb=0, ub=20) + z = model.addVar("z", obj=1, lb=0, ub=10) + w = model.addVar("w", obj=4, lb=0, ub=15) + + model.addCons(y - 2*x == 0) + model.addCons(x + z + w == 10) + model.addCons(x*y*z >= 21) # to prevent presolve from removing all variables + model.presolve() + + for v in model.getVars(True): + print(v.name, v.getStatus()) + + assert model.getNVars(True) == 1 + + model.freeTransform() + model.markDoNotMultaggrVar(w) + model.presolve() + assert model.getNVars(True) == 3 + + model.freeTransform() + model.markDoNotAggrVar(y) + model.presolve() + assert model.getNVars(True) == 4 + + assert x.getStatus() == "ORIGINAL" \ No newline at end of file From b2164592e7680c40d55f28fc14391c2d854b7b41 Mon Sep 17 00:00:00 2001 From: Joao-Dionisio Date: Wed, 12 Nov 2025 13:22:06 +0000 Subject: [PATCH 9/9] test getStatus as well --- tests/test_vars.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_vars.py b/tests/test_vars.py index 7f02f142d..7d129e325 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -112,7 +112,7 @@ def test_getNBranchingsCurrentRun(): assert n_branchings == m.getNNodes() - 1 -def test_markDoNotAggrVar(): +def test_markDoNotAggrVar_and_getStatus(): model = Model() x = model.addVar("x", obj=2, lb=0, ub=10) y = model.addVar("y", obj=3, lb=0, ub=20) @@ -124,19 +124,23 @@ def test_markDoNotAggrVar(): model.addCons(x*y*z >= 21) # to prevent presolve from removing all variables model.presolve() - for v in model.getVars(True): - print(v.name, v.getStatus()) + assert z.getStatus() == "ORIGINAL" + assert model.getTransformedVar(z).getStatus() == "AGGREGATED" + assert model.getTransformedVar(w).getStatus() == "MULTAGGR" assert model.getNVars(True) == 1 model.freeTransform() model.markDoNotMultaggrVar(w) model.presolve() + + assert model.getTransformedVar(w).getStatus() != "MULTAGGR" assert model.getNVars(True) == 3 model.freeTransform() model.markDoNotAggrVar(y) model.presolve() + assert model.getTransformedVar(z).getStatus() != "AGGREGATED" assert model.getNVars(True) == 4 assert x.getStatus() == "ORIGINAL" \ No newline at end of file