diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cea590d1..9912b1387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Unreleased +- `addConsNode`and `addConsLocal` now accept `Expr` +- add SCIP function: `delConsNode` + ## 3.1.1 - 2021-03-10 ### Added - add evaluation of `Expr` in `Solution`. diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index e374f56fd..0b2f0cb69 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -604,7 +604,7 @@ cdef extern from "scip/scip.h": SCIP_RETCODE SCIPaddVar(SCIP* scip, SCIP_VAR* var) SCIP_RETCODE SCIPdelVar(SCIP* scip, SCIP_VAR* var, SCIP_Bool* deleted) SCIP_RETCODE SCIPaddCons(SCIP* scip, SCIP_CONS* cons) - SCIP_RETCODE SCIPdelCons(SCIP* scip, SCIP_CONS* cons) + SCIP_RETCODE SCIPdelCons(SCIP* scip, SCIP_CONS* cons) SCIP_RETCODE SCIPsetObjsense(SCIP* scip, SCIP_OBJSENSE objsense) SCIP_OBJSENSE SCIPgetObjsense(SCIP* scip) SCIP_RETCODE SCIPsetObjlimit(SCIP* scip, SCIP_Real objlimit) @@ -782,7 +782,7 @@ cdef extern from "scip/scip.h": SCIP_CONSHDLR* SCIPconsGetHdlr(SCIP_CONS* cons) const char* SCIPconshdlrGetName(SCIP_CONSHDLR* conshdlr) SCIP_RETCODE SCIPdelConsLocal(SCIP* scip, SCIP_CONS* cons) - SCIP_RETCODE SCIPdelCons(SCIP* scip, SCIP_CONS* cons) + SCIP_RETCODE SCIPdelConsNode(SCIP* scip, SCIP_NODE* node, SCIP_CONS* cons) SCIP_RETCODE SCIPsetConsChecked(SCIP *scip, SCIP_CONS *cons, SCIP_Bool check) SCIP_RETCODE SCIPsetConsRemovable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool removable) SCIP_RETCODE SCIPsetConsInitial(SCIP *scip, SCIP_CONS *cons, SCIP_Bool initial) diff --git a/src/pyscipopt/scip.pyx b/src/pyscipopt/scip.pyx index 63d4b54fd..64139012e 100644 --- a/src/pyscipopt/scip.pyx +++ b/src/pyscipopt/scip.pyx @@ -1986,10 +1986,21 @@ cdef class Model: """ assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__ + kwargs = self._cons_kwargs(check, cons, dynamic, enforce, initial, local, modifiable, name, + propagate, removable, separate,stickingatnode) + cdef Constraint py_cons + py_cons = self._createCons(cons, **kwargs) + cdef SCIP_CONS* scip_cons + scip_cons = py_cons.scip_cons + PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + return py_cons + + def _cons_kwargs(self, check, cons, dynamic, enforce, initial, local, modifiable, name, propagate, removable, separate, + stickingatnode): # replace empty name with generic one if name == '': - name = 'c'+str(SCIPgetNConss(self._scip)+1) - + name = 'c' + str(SCIPgetNConss(self._scip) + 1) kwargs = dict(name=name, initial=initial, separate=separate, enforce=enforce, check=check, propagate=propagate, local=local, @@ -1997,21 +2008,26 @@ cdef class Model: removable=removable, stickingatnode=stickingatnode) kwargs['lhs'] = -SCIPinfinity(self._scip) if cons._lhs is None else cons._lhs - kwargs['rhs'] = SCIPinfinity(self._scip) if cons._rhs is None else cons._rhs + kwargs['rhs'] = SCIPinfinity(self._scip) if cons._rhs is None else cons._rhs + return kwargs + def _createCons(self, cons, **kwargs): deg = cons.expr.degree() + cdef Constraint py_cons + if deg <= 1: - return self._addLinCons(cons, **kwargs) + py_cons = self._createLinCons(cons, **kwargs) elif deg <= 2: - return self._addQuadCons(cons, **kwargs) + py_cons = self._createQuadCons(cons, **kwargs) elif deg == float('inf'): # general nonlinear - return self._addGenNonlinearCons(cons, **kwargs) + return self._createGenNonlinearCons(cons, **kwargs) else: - return self._addNonlinearCons(cons, **kwargs) + return self._createNonlinearCons(cons, **kwargs) + return py_cons - def _addLinCons(self, ExprCons lincons, **kwargs): - assert isinstance(lincons, ExprCons), "given constraint is not ExprCons but %s" % lincons.__class__.__name__ + def _createLinCons(self, ExprCons lincons, **kwargs): + assert isinstance(lincons, ExprCons), "given constraint is not ExprCons but %s" % lincons.__class__.__name__ assert lincons.expr.degree() <= 1, "given constraint is not linear, degree == %d" % lincons.expr.degree() terms = lincons.expr.terms @@ -2032,17 +2048,12 @@ cdef class Model: kwargs['separate'], kwargs['enforce'], kwargs['check'], kwargs['propagate'], kwargs['local'], kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode'])) - - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - PyCons = Constraint.create(scip_cons) - PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) - free(vars_array) free(coeffs_array) - return PyCons + return Constraint.create(scip_cons) - def _addQuadCons(self, ExprCons quadcons, **kwargs): + def _createQuadCons(self, ExprCons quadcons, **kwargs): terms = quadcons.expr.terms assert quadcons.expr.degree() <= 2, "given constraint is not quadratic, degree == %d" % quadcons.expr.degree() @@ -2065,12 +2076,9 @@ cdef class Model: var1, var2 = v[0], v[1] PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.scip_var, var2.scip_var, c)) - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - PyCons = Constraint.create(scip_cons) - PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) - return PyCons + return Constraint.create(scip_cons) - def _addNonlinearCons(self, ExprCons cons, **kwargs): + def _createNonlinearCons(self, ExprCons cons, **kwargs): cdef SCIP_EXPR* expr cdef SCIP_EXPR** varexprs cdef SCIP_EXPRDATA_MONOMIAL** monomials @@ -2123,16 +2131,13 @@ cdef class Model: kwargs['check'], kwargs['propagate'], kwargs['local'], kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode']) ) - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) - PyCons = Constraint.create(scip_cons) - PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) PY_SCIP_CALL( SCIPexprtreeFree(&exprtree) ) free(vars) free(monomials) free(varexprs) - return PyCons + return Constraint.create(scip_cons) - def _addGenNonlinearCons(self, ExprCons cons, **kwargs): + def _createGenNonlinearCons(self, ExprCons cons, **kwargs): cdef SCIP_EXPR** childrenexpr cdef SCIP_EXPR** scipexprs cdef SCIP_EXPRTREE* exprtree @@ -2216,16 +2221,14 @@ cdef class Model: kwargs['check'], kwargs['propagate'], kwargs['local'], kwargs['modifiable'], kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode']) ) - PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons)) PyCons = Constraint.create(scip_cons) - PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) PY_SCIP_CALL( SCIPexprtreeFree(&exprtree) ) # free more memory free(scipexprs) free(vars) - return PyCons + return Constraint.create(scip_cons) def addConsCoeff(self, Constraint cons, Variable var, coeff): """Add coefficient to the linear constraint (if non-zero). @@ -2237,32 +2240,72 @@ cdef class Model: """ PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.scip_cons, var.scip_var, coeff)) - def addConsNode(self, Node node, Constraint cons, Node validnode=None): + def addConsNode(self, Node node, cons, Node validnode=None, name="", initial=True, separate=True, + enforce=False, check=False, propagate=True, local=True, + modifiable=False, dynamic=False, removable=False, stickingatnode=True): """Add a constraint to the given node :param Node node: node to add the constraint to :param Constraint cons: constraint to add :param Node validnode: more global node where cons is also valid - + Note: Additional parameter for constraint settings get ignored if cons is not an ExprCons! """ + cdef Constraint py_cons + cdef SCIP_CONS* scip_cons + if isinstance(cons, Constraint): + py_cons = cons + scip_cons = py_cons.scip_cons + # TODO: (Why) Is this INCREF necessary? + Py_INCREF(cons) + elif isinstance(cons, ExprCons): + kwargs = self._cons_kwargs(check, cons, dynamic, enforce, initial, local, modifiable, name, + propagate, removable, separate,stickingatnode) + py_cons = self._createCons(cons, **kwargs) + scip_cons = py_cons.scip_cons + else: + raise Warning(f"Argument cons is of incorrect type ({type(cons)}).") + if isinstance(validnode, Node): - PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, validnode.scip_node)) + PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, scip_cons, validnode.scip_node)) + elif validnode is None: + PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, scip_cons, NULL)) else: - PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, NULL)) - Py_INCREF(cons) + raise Warning(f"Argument validnode is of incorrect type ({type(validnode)}).") + # TODO: This is needed for the ExprCons case. Is it a problem for the Constraint case? + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + return py_cons - def addConsLocal(self, Constraint cons, Node validnode=None): + def addConsLocal(self, Constraint cons, Node validnode=None, name="", initial=True, separate=True, + enforce=False, check=False, propagate=True, local=True, + modifiable=False, dynamic=False, removable=False, stickingatnode=False): """Add a constraint to the current node :param Constraint cons: constraint to add :param Node validnode: more global node where cons is also valid - + Note: Additional parameter for constraint settings get ignored if cons is not an ExprCons! """ + cdef Constraint py_cons + cdef SCIP_CONS* scip_cons + if isinstance(cons, Constraint): + py_cons = cons + scip_cons = py_cons.scip_cons + Py_INCREF(cons) + elif isinstance(cons, ExprCons): + kwargs = self._cons_kwargs(check, cons, dynamic, enforce, initial, local, modifiable, name, + propagate, removable, separate,stickingatnode) + py_cons = self._createCons(cons, **kwargs) + scip_cons = py_cons.scip_cons + else: + raise Warning(f"Argument cons is of incorrect type ({type(cons)}).") + if isinstance(validnode, Node): - PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, validnode.scip_node)) + PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, validnode.scip_node)) + elif validnode is None: + PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, NULL)) else: - PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL)) - Py_INCREF(cons) + raise Warning(f"Argument validnode is of incorrect type ({type(validnode)}).") + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons)) + return py_cons def addConsSOS1(self, vars, weights=None, name="SOS1cons", initial=True, separate=True, enforce=True, check=True, @@ -2937,6 +2980,15 @@ cdef class Model: """ PY_SCIP_CALL(SCIPdelConsLocal(self._scip, cons.scip_cons)) + def delConsNode(self, Node node, Constraint cons): + """Delete constraint from the given node and it's children + + :param Constraint cons: constraint to be deleted + :param Node node: node to delete the constraint from + + """ + PY_SCIP_CALL(SCIPdelConsNode(self._scip, node.scip_node, cons.scip_cons)) + def getValsLinear(self, Constraint cons): """Retrieve the coefficients of a linear constraint