Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased
### Added
- Wrapped `SCIPcaptureVar()`, `SCIPreleaseVar()`, `SCIPvarGetNUses()`, `SCIPcaptureCons()`, `SCIPreleaseCons()`, and `SCIPconsGetNUses()`
### Fixed
### Changed
- Speed up `Expr.__add__` and `Expr.__iadd__` via the C-level API
Expand Down
4 changes: 3 additions & 1 deletion src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -818,8 +818,9 @@ cdef extern from "scip/scip.h":
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)
int SCIPvarGetNUses(SCIP_VAR* var)
SCIP_RETCODE SCIPaddPricedVar(SCIP* scip, SCIP_VAR* var, SCIP_Real score)
SCIP_RETCODE SCIPtransformVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar)
SCIP_RETCODE SCIPgetTransformedVar(SCIP* scip, SCIP_VAR* var, SCIP_VAR** transvar)
SCIP_RETCODE SCIPaddVarLocks(SCIP* scip, SCIP_VAR* var, int nlocksdown, int nlocksup)
Expand Down Expand Up @@ -913,6 +914,7 @@ cdef extern from "scip/scip.h":
# Constraint Methods
SCIP_RETCODE SCIPcaptureCons(SCIP* scip, SCIP_CONS* cons)
SCIP_RETCODE SCIPreleaseCons(SCIP* scip, SCIP_CONS** cons)
int SCIPconsGetNUses(SCIP_CONS* cons)
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)
Expand Down
116 changes: 115 additions & 1 deletion src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -4636,6 +4636,63 @@ cdef class Model:
var.scip_var = NULL
return deleted

def captureVar(self, Variable var):
"""
Increase the usage counter of a variable. Must be paired with a later releaseVar.

Parameters
----------
var : Variable
variable to capture

"""
PY_SCIP_CALL(SCIPcaptureVar(self._scip, var.scip_var))

def releaseVar(self, Variable var):
"""
Decrease the usage counter of a variable.

Unlike the underlying ``SCIPreleaseVar``, this wrapper refuses to
release the last reference. It must be paired with a prior
captureVar call. This guarantees the variable is never freed via
this method and the wrapper's pointer stays valid.

Parameters
----------
var : Variable
variable to release

Raises
------
Exception
if releasing would free the variable (no matching captureVar).

"""
cdef SCIP_VAR* scip_var = var.scip_var
if SCIPvarGetNUses(scip_var) <= 1:
raise Exception(
"releaseVar would free the variable; must be paired with "
"a prior captureVar call."
)
PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var))

def getVarNUses(self, Variable var):
"""
Get the number of times the variable is currently captured.

Parameters
----------
var : Variable
variable to query

Returns
-------
int
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incomplete.

the current usage count.

"""
return SCIPvarGetNUses(var.scip_var)

def aggregateVars(self, Variable varx, Variable vary, coefx=1.0, coefy=-1.0, rhs=0.0):
"""
Aggregate two variables by adding an aggregation constraint.
Expand Down Expand Up @@ -8568,7 +8625,64 @@ cdef class Model:

"""
PY_SCIP_CALL(SCIPdelConsLocal(self._scip, cons.scip_cons))


def captureCons(self, Constraint cons):
"""
Increase the usage counter of a constraint. Must be paired with a later releaseCons.

Parameters
----------
cons : Constraint
constraint to capture

"""
PY_SCIP_CALL(SCIPcaptureCons(self._scip, cons.scip_cons))

def releaseCons(self, Constraint cons):
"""
Decrease the usage counter of a constraint.

Unlike the underlying ``SCIPreleaseCons``, this wrapper refuses to
release the last reference. It must be paired with a prior
captureCons call. This guarantees the constraint is never freed via
this method and the wrapper's pointer stays valid.

Parameters
----------
cons : Constraint
constraint to release
Comment thread
DominikKamp marked this conversation as resolved.

Raises
------
Exception
if releasing would free the constraint (no matching captureCons).

"""
cdef SCIP_CONS* scip_cons = cons.scip_cons
if SCIPconsGetNUses(scip_cons) <= 1:
raise Exception(
"releaseCons would free the constraint; must be paired with "
"a prior captureCons call."
)
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))

def getConsNUses(self, Constraint cons):
"""
Get the number of times the constraint is currently captured.

Parameters
----------
cons : Constraint
constraint to query

Returns
-------
int
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incomplete.

the current usage count.

"""
return SCIPconsGetNUses(cons.scip_cons)

def getValsLinear(self, Constraint cons):
"""
Retrieve the coefficients of a linear constraint
Expand Down
6 changes: 6 additions & 0 deletions src/pyscipopt/scip.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,8 @@ class Model:
def calcNodeselPriority(
self, variable: Incomplete, branchdir: Incomplete, targetvalue: Incomplete
) -> Incomplete: ...
def captureCons(self, cons: Incomplete) -> Incomplete: ...
def captureVar(self, var: Incomplete) -> Incomplete: ...
def catchEvent(
self, eventtype: Incomplete, eventhdlr: Incomplete
) -> Incomplete: ...
Expand Down Expand Up @@ -1113,6 +1115,7 @@ class Model:
def getChildren(self) -> Incomplete: ...
def getColRedCost(self, col: Incomplete) -> Incomplete: ...
def getCondition(self, exact: Incomplete = ...) -> Incomplete: ...
def getConsNUses(self, cons: Incomplete) -> Incomplete: ...
def getConsNVars(self, constraint: Incomplete) -> Incomplete: ...
def getConsVals(self, constraint: Incomplete) -> Incomplete: ...
def getConsVars(self, constraint: Incomplete) -> Incomplete: ...
Expand Down Expand Up @@ -1257,6 +1260,7 @@ class Model:
def getValsLinear(self, cons: Incomplete) -> Incomplete: ...
def getVarDict(self, transformed: Incomplete = ...) -> Incomplete: ...
def getVarLbDive(self, var: Incomplete) -> Incomplete: ...
def getVarNUses(self, var: Incomplete) -> Incomplete: ...
def getVarPseudocost(
self, var: Incomplete, branchdir: Incomplete
) -> Incomplete: ...
Expand Down Expand Up @@ -1487,7 +1491,9 @@ class Model:
def readSolFile(self, filename: Incomplete) -> Incomplete: ...
def redirectOutput(self) -> Incomplete: ...
def relax(self) -> Incomplete: ...
def releaseCons(self, cons: Incomplete) -> Incomplete: ...
def releaseRow(self, row: Incomplete) -> Incomplete: ...
def releaseVar(self, var: Incomplete) -> Incomplete: ...
def repropagateNode(self, node: Incomplete) -> Incomplete: ...
def resetParam(self, name: Incomplete) -> Incomplete: ...
def resetParams(self) -> Incomplete: ...
Expand Down
17 changes: 17 additions & 0 deletions tests/test_cons.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,20 @@ def test_getValsLinear():
@pytest.mark.skip(reason="TODO: test getRowLinear()")
def test_getRowLinear():
assert True


def test_captureCons_releaseCons():
m = Model()
x = m.addVar("x")
c = m.addCons(x <= 1)

# Pair capture+release: problem still holds its own capture, so this is safe.
assert m.getConsNUses(c) == 1
m.captureCons(c)
assert m.getConsNUses(c) == 2
m.releaseCons(c)
assert m.getConsNUses(c) == 1

Comment thread
Joao-Dionisio marked this conversation as resolved.
# Model continues to function — problem's capture survived.
m.optimize()
assert m.getStatus() == "optimal"
17 changes: 16 additions & 1 deletion tests/test_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,19 @@ def test_adjustedVarUb():
# For continuous variables, values should generally stay the same
x_cont = m.addVar(vtype='C', lb=-10.0, ub=10.0, name="x_cont")
assert m.adjustedVarUb(x_cont, 5.5) == 5.5
assert m.adjustedVarUb(x_cont, -3.2) == -3.2
assert m.adjustedVarUb(x_cont, -3.2) == -3.2


def test_captureVar_releaseVar():
m = Model()
x = m.addVar("x")

# Pair capture+release: problem still holds its own capture, so this is safe.
assert m.getVarNUses(x) == 1
m.captureVar(x)
assert m.getVarNUses(x) == 2
m.releaseVar(x)
assert m.getVarNUses(x) == 1

m.optimize()
assert m.getStatus() == "optimal"
Loading