diff --git a/CHANGELOG.md b/CHANGELOG.md index da9fc99fa..ab841d2de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +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. +- 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 3f6b5ce15..7d99e4715 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 @@ -768,6 +778,8 @@ 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) SCIP_RETCODE SCIPreleaseVar(SCIP* scip, SCIP_VAR** var) @@ -790,8 +802,10 @@ 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_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 72dc85d2b..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.""" @@ -1538,6 +1537,32 @@ cdef class Variable(Expr): elif vartype == SCIP_VARTYPE_IMPLINT: return "IMPLINT" + def getStatus(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 @@ -1549,6 +1574,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. @@ -1560,7 +1596,6 @@ cdef class Variable(Expr): """ return SCIPvarIsInLP(self.scip_var) - def getIndex(self): """ Retrieve the unique index of the variable. @@ -2043,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""" @@ -2584,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, @@ -2634,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 @@ -2817,7 +2849,6 @@ cdef class Model: """ return SCIPminorVersion() - def getTechVersion(self): """ Retrieve SCIP technical version. @@ -3559,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)) @@ -3905,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. @@ -4385,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. @@ -4413,6 +4441,28 @@ 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. + + Parameters + ---------- + var : Variable + variable to mark + """ + PY_SCIP_CALL(SCIPmarkDoNotMultaggrVar(self._scip, var.scip_var)) + def getVars(self, transformed=False): """ Retrieve all variables. @@ -4754,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): """ @@ -4767,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 @@ -7044,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, @@ -7627,7 +7674,6 @@ cdef class Model: return activity - def getSlack(self, Constraint cons, Solution sol = None, side = None): """ Retrieve slack of given constraint. @@ -8648,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. @@ -9285,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, @@ -9329,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 """ @@ -9388,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. @@ -10992,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 @@ -11482,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): """ diff --git a/tests/test_vars.py b/tests/test_vars.py index 604c7870b..7d129e325 100644 --- a/tests/test_vars.py +++ b/tests/test_vars.py @@ -111,3 +111,36 @@ def test_getNBranchingsCurrentRun(): n_branchings += var.getNBranchingsCurrentRun(SCIP_BRANCHDIR.DOWNWARDS) assert n_branchings == m.getNNodes() - 1 + +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) + 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() + + 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