diff --git a/CHANGELOG.md b/CHANGELOG.md index b55ccad09..c35102ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ - Added addMatrixConsIndicator(), and tests - Added SCIPvarMarkRelaxationOnly, SCIPvarIsRelaxationOnly, SCIPvarMarkDeletable, SCIPvarIsDeletable, and tests - Wrapped SCIPgetNLPBranchCands +- Added getConsVals() to get coefficients of any linear type constraint +- Generalized getLhs() and getRhs() to additionally support any linear type constraint ### Fixed - Raised an error when an expression is used when a variable is required ### Changed diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index 2d20d5ff3..36ab1c53b 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -858,6 +858,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPtransformCons(SCIP* scip, SCIP_CONS* cons, SCIP_CONS** transcons) SCIP_RETCODE SCIPgetTransformedCons(SCIP* scip, SCIP_CONS* cons, SCIP_CONS** transcons) SCIP_RETCODE SCIPgetConsVars(SCIP* scip, SCIP_CONS* cons, SCIP_VAR** vars, int varssize, SCIP_Bool* success) + SCIP_RETCODE SCIPgetConsVals(SCIP* scip, SCIP_CONS* cons, SCIP_Real* vals, int valssize, SCIP_Bool* success) SCIP_RETCODE SCIPgetConsNVars(SCIP* scip, SCIP_CONS* cons, int* nvars, SCIP_Bool* success) SCIP_CONS** SCIPgetConss(SCIP* scip) const char* SCIPconsGetName(SCIP_CONS* cons) @@ -1477,6 +1478,8 @@ cdef extern from "scip/cons_linear.h": SCIP_RETCODE SCIPchgRhsLinear(SCIP* scip, SCIP_CONS* cons, SCIP_Real rhs) SCIP_Real SCIPgetLhsLinear(SCIP* scip, SCIP_CONS* cons) SCIP_Real SCIPgetRhsLinear(SCIP* scip, SCIP_CONS* cons) + SCIP_Real SCIPconsGetLhs(SCIP* scip, SCIP_CONS* cons, SCIP_Bool* success) + SCIP_Real SCIPconsGetRhs(SCIP* scip, SCIP_CONS* cons, SCIP_Bool* success) SCIP_RETCODE SCIPchgCoefLinear(SCIP* scip, SCIP_CONS* cons, SCIP_VAR* var, SCIP_Real newval) SCIP_RETCODE SCIPdelCoefLinear(SCIP* scip, SCIP_CONS* cons, SCIP_VAR*) SCIP_RETCODE SCIPaddCoefLinear(SCIP* scip, SCIP_CONS* cons, SCIP_VAR*, SCIP_Real val) diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index b615f96f2..ad9af85b8 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -2196,6 +2196,18 @@ cdef class Constraint: constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') return constype == 'knapsack' + def isLinearType(self): + """ + Returns True if constraint can be represented as a linear constraint. + + Returns + ------- + bool + + """ + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') + return constype in ('linear', 'knapsack', 'setppc', 'logicor', 'varbound') + def isNonlinear(self): """ Returns True if constraint is nonlinear. @@ -6019,7 +6031,11 @@ cdef class Model: SCIPgetConsNVars(self._scip, constraint.scip_cons, &nvars, &success) _vars = malloc(nvars * sizeof(SCIP_VAR*)) - SCIPgetConsVars(self._scip, constraint.scip_cons, _vars, nvars*sizeof(SCIP_VAR*), &success) + SCIPgetConsVars(self._scip, constraint.scip_cons, _vars, nvars, &success) + + if not success: + free(_vars) + return None vars = [] for i in range(nvars): @@ -6034,7 +6050,39 @@ cdef class Model: self._modelvars[ptr] = var vars.append(var) + free(_vars) return vars + + def getConsVals(self, Constraint constraint): + """ + Returns the value array of an arbitrary SCIP constraint that can be represented as a single linear constraint. + + Parameters + ---------- + constraint : Constraint + Constraint to get the values from. + + Returns + ------- + list of float + + """ + cdef SCIP_Real* _vals + cdef int nvars + cdef SCIP_Bool success + cdef int i + + nvars = self.getConsNVars(constraint) + _vals = malloc(nvars * sizeof(SCIP_Real)) + PY_SCIP_CALL(SCIPgetConsVals(self._scip, constraint.scip_cons, _vals, nvars, &success)) + + if not success: + free(_vals) + return None + + vals = [_vals[i] for i in range(nvars)] + free(_vals) + return vals def getNVarsAnd(self, Constraint and_cons): """ @@ -6050,8 +6098,6 @@ cdef class Model: int """ - cdef int nvars - cdef SCIP_Bool success return SCIPgetNVarsAnd(self._scip, and_cons.scip_cons) @@ -6069,9 +6115,9 @@ cdef class Model: list of Variable """ + cdef SCIP_VAR** _vars cdef int nvars - cdef SCIP_Bool success cdef int i constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(and_cons.scip_cons))).decode('UTF-8') @@ -6109,8 +6155,8 @@ cdef class Model: Variable """ + cdef SCIP_VAR* _resultant - cdef SCIP_Bool success _resultant = SCIPgetResultantAnd(self._scip, and_cons.scip_cons) @@ -6140,8 +6186,7 @@ cdef class Model: bool """ - cdef SCIP_Bool success - + return SCIPisAndConsSorted(self._scip, and_cons.scip_cons) def sortAndCons(self, Constraint and_cons): @@ -6154,7 +6199,6 @@ cdef class Model: Constraint to sort. """ - cdef SCIP_Bool success PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons)) @@ -7270,9 +7314,13 @@ cdef class Model: float """ + cdef SCIP_Bool success constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') - if constype == 'linear': - return SCIPgetRhsLinear(self._scip, cons.scip_cons) + + if cons.isLinearType(): + rhs = SCIPconsGetRhs(self._scip, cons.scip_cons, &success) + assert(success) + return rhs elif constype == 'nonlinear': return SCIPgetRhsNonlinear(cons.scip_cons) else: @@ -7311,9 +7359,13 @@ cdef class Model: float """ + cdef SCIP_Bool success constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') - if constype == 'linear': - return SCIPgetLhsLinear(self._scip, cons.scip_cons) + + if cons.isLinearType(): + lhs = SCIPconsGetLhs(self._scip, cons.scip_cons, &success) + assert(success) + return lhs elif constype == 'nonlinear': return SCIPgetLhsNonlinear(cons.scip_cons) else: diff --git a/tests/test_cons.py b/tests/test_cons.py index 188095b23..36a0dfcec 100644 --- a/tests/test_cons.py +++ b/tests/test_cons.py @@ -27,6 +27,23 @@ def test_getConsVars(): c = m.addCons(quicksum(x[i] for i in x) <= 1) assert m.getConsVars(c) == [x[i] for i in x] +def test_getConsVals(): + n_vars = 100 + m = Model() + x = {} + for i in range(n_vars): + x[i] = m.addVar("%i" % i, vtype="B") + + c1 = m.addCons(quicksum(x[i] for i in x) <= 1) + c2 = m.addConsKnapsack([x[i] for i in x], [i for i in range(1, n_vars+1)], 10) + vals1 = m.getConsVals(c1) + vals2 = m.getConsVals(c2) + + assert len(vals1) == n_vars + assert all(isinstance(v, float) for v in vals1) + assert len(vals2) == n_vars + assert all(isinstance(v, float) for v in vals2) + assert m.getConsVals(c2) == [i for i in range(1, n_vars+1)] def test_constraint_option_setting(): m = Model() @@ -266,6 +283,8 @@ def test_cons_knapsack(): m.chgCapacityKnapsack(knapsack_cons, 5) assert m.getCapacityKnapsack(knapsack_cons) == 5 + assert m.getRhs(knapsack_cons) == 5 + assert m.getLhs(knapsack_cons) == -m.infinity() m.addCoefKnapsack(knapsack_cons, z, 3) weights = m.getWeightsKnapsack(knapsack_cons)