From d7644a6284d19280584693b483ec328b3752a7f0 Mon Sep 17 00:00:00 2001 From: I570595 Date: Tue, 20 May 2025 12:50:07 +0200 Subject: [PATCH] add constraint cumulative --- src/pyscipopt/scip.pxd | 41 ++++++++++++ src/pyscipopt/scip.pxi | 148 ++++++++++++++++++++++++++++++++++++++++- tests/test_cons.py | 15 +++++ 3 files changed, 203 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pxd b/src/pyscipopt/scip.pxd index f00f9a664..d467d03ba 100644 --- a/src/pyscipopt/scip.pxd +++ b/src/pyscipopt/scip.pxd @@ -1505,6 +1505,47 @@ cdef extern from "scip/cons_knapsack.h": int SCIPgetNVarsKnapsack(SCIP* scip, SCIP_CONS* cons) SCIP_Longint* SCIPgetWeightsKnapsack(SCIP* scip, SCIP_CONS* cons) +cdef extern from "scip/cons_cumulative.h": + SCIP_RETCODE SCIPcreateConsCumulative(SCIP* scip, + SCIP_CONS** cons, + char* name, + int nvars, + SCIP_VAR** vars, + int* durations, + int* demands, + int capacity, + SCIP_Bool initial, + SCIP_Bool separate, + SCIP_Bool enforce, + SCIP_Bool check, + SCIP_Bool propagate, + SCIP_Bool local, + SCIP_Bool modifiable, + SCIP_Bool dynamic, + SCIP_Bool removable, + SCIP_Bool stickingatnode) + + SCIP_RETCODE SCIPcreateConsBasicCumulative(SCIP* scip, SCIP_CONS** cons, + char* name, + int nvars, + SCIP_VAR** vars, + int* durations, + int* demands, + int capacity) + + SCIP_RETCODE SCIPsetHminCumulative(SCIP* scip, SCIP_CONS* cons, int hmin) + SCIP_RETCODE SCIPsetHmaxCumulative(SCIP* scip, SCIP_CONS* cons, int hmax) + + int SCIPgetHminCumulative(SCIP* scip, SCIP_CONS* cons) + int SCIPgetHmaxCumulative(SCIP* scip, SCIP_CONS* cons) + + SCIP_VAR** SCIPgetVarsCumulative(SCIP* scip, SCIP_CONS* cons) + int SCIPgetNVarsCumulative(SCIP* scip, SCIP_CONS* cons) + int* SCIPgetDurationsCumulative(SCIP* scip, SCIP_CONS* cons) + int* SCIPgetDemandsCumulative(SCIP* scip, SCIP_CONS* cons) + int SCIPgetCapacityCumulative(SCIP* scip, SCIP_CONS* cons) + + cdef extern from "scip/cons_nonlinear.h": SCIP_EXPR* SCIPgetExprNonlinear(SCIP_CONS* cons) SCIP_RETCODE SCIPcreateConsNonlinear(SCIP* scip, diff --git a/src/pyscipopt/scip.pxi b/src/pyscipopt/scip.pxi index 970e6f039..3c0dddae5 100644 --- a/src/pyscipopt/scip.pxi +++ b/src/pyscipopt/scip.pxi @@ -2080,6 +2080,18 @@ cdef class Constraint: constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') return constype == 'linear' + def isCumulative(self): + """ + Returns True if constraint is cumulative + + Returns + ------- + bool + + """ + constype = bytes(SCIPconshdlrGetName(SCIPconsGetHdlr(self.scip_cons))).decode('UTF-8') + return constype == 'cumulative' + def isKnapsack(self): """ Returns True if constraint is a knapsack constraint. @@ -5872,7 +5884,50 @@ cdef class Model: else: PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL)) Py_INCREF(cons) - + + def addConsCumulative(self, vars, durations, demands, capacity, name="", + initial=True, separate=True, enforce=True, check=True, + propagate=True, local=False, modifiable=False, + dynamic=False, removable=False, stickingatnode=False): + + cdef int n = len(vars) + assert n == len(durations) == len(demands) + + if name == "": + name = "c" + str(SCIPgetNConss(self._scip) + 1) + + cdef SCIP_VAR** c_vars = malloc(n * sizeof(SCIP_VAR*)) + cdef int* c_durs = malloc(n * sizeof(int)) + cdef int* c_dem = malloc(n * sizeof(int)) + cdef SCIP_CONS* cons + cdef int i + + for i in range(n): + c_vars[i] = ( vars[i]).scip_var + c_durs[i] = durations[i] + c_dem[i] = demands[i] + + # --- Constraint erzeugen + PY_SCIP_CALL(SCIPcreateConsCumulative( + self._scip, &cons, str_conversion(name), + n, c_vars, c_durs, c_dem, capacity, + initial, separate, enforce, check, propagate, + local, modifiable, dynamic, removable, stickingatnode)) + + # --- Constraint dem Modell hinzufügen (verhindert spätere Segfaults!) + PY_SCIP_CALL(SCIPaddCons(self._scip, cons)) + + # --- Hilfs‑Arrays freigeben + free(c_vars) + free(c_durs) + free(c_dem) + + # --- Python‑Wrapper erstellen und zurückgeben + pyCons = Constraint.create(cons) + PY_SCIP_CALL(SCIPreleaseCons(self._scip, &cons)) + return pyCons + + def addConsKnapsack(self, vars, weights, capacity, name="", initial=True, separate=True, enforce=True, check=True, modifiable=False, propagate=True, local=False, dynamic=False, @@ -6632,6 +6687,97 @@ cdef class Model: else: raise Warning("method cannot be called for constraints of type " + constype) + def getCapacityCumulative(self, Constraint cons): + """ + Liefert die Kapazität einer cumulative‑Constraint. + """ + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative', \ + "Methode nur für cumulative‑Constraints geeignet" + return SCIPgetCapacityCumulative(self._scip, cons.scip_cons) + + def getNVarsCumulative(self, Constraint cons): + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + return SCIPgetNVarsCumulative(self._scip, cons.scip_cons) + + def getVarsCumulative(self, Constraint cons): + """ + Gibt die Startzeit‑Variablen als Python‑Liste zurück. + """ + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + cdef int nvars = SCIPgetNVarsCumulative(self._scip, cons.scip_cons) + cdef SCIP_VAR** _vars = SCIPgetVarsCumulative(self._scip, + cons.scip_cons) + + return [ Variable.create(_vars[i]) for i in range(nvars) ] + + def getDurationsCumulative(self, Constraint cons): + """ + Dict {varname: duration} + """ + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + cdef int nvars = SCIPgetNVarsCumulative(self._scip, cons.scip_cons) + cdef SCIP_VAR** _vars = SCIPgetVarsCumulative(self._scip, + cons.scip_cons) + cdef int* _durs = SCIPgetDurationsCumulative(self._scip, + cons.scip_cons) + + cdef dict durs = {} + cdef int i + for i in range(nvars): + durs[ bytes(SCIPvarGetName(_vars[i])).decode('utf-8') ] = _durs[i] + return durs + + def getDemandsCumulative(self, Constraint cons): + """ + Dict {varname: demand} + """ + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + cdef int nvars = SCIPgetNVarsCumulative(self._scip, cons.scip_cons) + cdef SCIP_VAR** _vars = SCIPgetVarsCumulative(self._scip, + cons.scip_cons) + cdef int* _dem = SCIPgetDemandsCumulative(self._scip, + cons.scip_cons) + + cdef dict demands = {} + cdef int i + for i in range(nvars): + demands[ bytes(SCIPvarGetName(_vars[i])).decode('utf-8') ] = _dem[i] + return demands + + def getHminCumulative(self, Constraint cons): + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + return SCIPgetHminCumulative(self._scip, cons.scip_cons) + + def setHminCumulative(self, Constraint cons, hmin): + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + PY_SCIP_CALL(SCIPsetHminCumulative(self._scip, cons.scip_cons, hmin)) + + def getHmaxCumulative(self, Constraint cons): + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + return SCIPgetHmaxCumulative(self._scip, cons.scip_cons) + + def setHmaxCumulative(self, Constraint cons, hmax): + constype = bytes(SCIPconshdlrGetName( + SCIPconsGetHdlr(cons.scip_cons))).decode('UTF-8') + assert constype == 'cumulative' + PY_SCIP_CALL(SCIPsetHmaxCumulative(self._scip, cons.scip_cons, hmax)) + def chgCapacityKnapsack(self, Constraint cons, capacity): """ Change capacity of a knapsack constraint. diff --git a/tests/test_cons.py b/tests/test_cons.py index a3c663ee0..adcbba4c1 100644 --- a/tests/test_cons.py +++ b/tests/test_cons.py @@ -98,6 +98,21 @@ def test_SOScons(): assert c1.getConshdlrName() == "SOS1" assert c2.getConshdlrName() == "SOS2" +def test_cons_cumulative(): + m = Model() + + s1 = m.addVar("s1", vtype="I") + s2 = m.addVar("s2", vtype="I") + s3 = m.addVar("s3", vtype="I") + + dur = [3, 2, 4] + dem = [2, 1, 3] + cap = 4 + + c = m.addConsCumulative([s1, s2, s3], dur, dem, cap) + assert c.isCumulative() + assert m.getCapacityCumulative(c) == cap + assert m.getDurationsCumulative(c) == { "s1": 3, "s2": 2, "s3": 4 } def test_cons_indicator(): m = Model()