Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
- Added `getBase()` and `setBase()` methods to `LP` class for getting/setting basis status
- Added `getMemUsed()`, `getMemTotal()`, and `getMemExternEstim()` methods
### Fixed
- Removed `Py_INCREF`/`Py_DECREF` on `Model` in `catchEvent`/`dropEvent` that caused memory leak for imbalanced usage
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from pyscipopt.scip import LP as LP
from pyscipopt.scip import IISfinder as IISfinder
from pyscipopt.scip import PY_SCIP_LPPARAM as SCIP_LPPARAM
from pyscipopt.scip import PY_SCIP_BASESTAT as SCIP_BASESTAT
from pyscipopt.scip import readStatistics as readStatistics
from pyscipopt.scip import Expr as Expr
from pyscipopt.scip import MatrixExpr as MatrixExpr
Expand Down
56 changes: 56 additions & 0 deletions src/pyscipopt/lp.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,62 @@ cdef class LP:

return binds

def getBase(self):
"""Returns the basis status of columns and rows.

Status values are defined in SCIP_BASESTAT: LOWER, BASIC, UPPER, ZERO.

Returns
-------
tuple of (list of int, list of int)
Column basis statuses and row basis statuses.

"""
cdef int ncols = self.ncols()
cdef int nrows = self.nrows()
cdef int* c_cstat = <int*> malloc(ncols * sizeof(int))
cdef int* c_rstat = <int*> malloc(nrows * sizeof(int))
cdef int i

PY_SCIP_CALL(SCIPlpiGetBase(self.lpi, c_cstat, c_rstat))

cstat = [c_cstat[i] for i in range(ncols)]
rstat = [c_rstat[i] for i in range(nrows)]

free(c_rstat)
free(c_cstat)

Comment thread
Joao-Dionisio marked this conversation as resolved.
return cstat, rstat

def setBase(self, cstat, rstat):
"""Sets the basis status of columns and rows.

Status values are defined in SCIP_BASESTAT: LOWER, BASIC, UPPER, ZERO.

Parameters
----------
cstat : list of int
Column basis statuses (length must equal ncols).
rstat : list of int
Row basis statuses (length must equal nrows).

"""
cdef int ncols = len(cstat)
cdef int nrows = len(rstat)
cdef int* c_cstat = <int*> malloc(ncols * sizeof(int))
cdef int* c_rstat = <int*> malloc(nrows * sizeof(int))
cdef int i

for i in range(ncols):
c_cstat[i] = cstat[i]
for i in range(nrows):
c_rstat[i] = rstat[i]

PY_SCIP_CALL(SCIPlpiSetBase(self.lpi, c_cstat, c_rstat))
Comment thread
Joao-Dionisio marked this conversation as resolved.
Outdated

free(c_rstat)
free(c_cstat)
Comment thread
Joao-Dionisio marked this conversation as resolved.

# Parameter Methods

def setIntParam(self, param, value):
Expand Down
2 changes: 2 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,8 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE SCIPlpiGetPrimalRay(SCIP_LPI* lpi, SCIP_Real* ray)
SCIP_RETCODE SCIPlpiGetDualfarkas(SCIP_LPI* lpi, SCIP_Real* dualfarkas)
SCIP_RETCODE SCIPlpiGetBasisInd(SCIP_LPI* lpi, int* bind)
SCIP_RETCODE SCIPlpiGetBase(SCIP_LPI* lpi, int* cstat, int* rstat)
SCIP_RETCODE SCIPlpiSetBase(SCIP_LPI* lpi, const int* cstat, const int* rstat)
SCIP_RETCODE SCIPlpiGetRealSolQuality(SCIP_LPI* lpi, SCIP_LPSOLQUALITY qualityindicator, SCIP_Real* quality)
SCIP_RETCODE SCIPlpiGetIntpar(SCIP_LPI* lpi, SCIP_LPPARAM type, int* ival)
SCIP_RETCODE SCIPlpiGetRealpar(SCIP_LPI* lpi, SCIP_LPPARAM type, SCIP_Real* dval)
Expand Down
6 changes: 6 additions & 0 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@
POLISHING = SCIP_LPPAR_POLISHING
REFACTOR = SCIP_LPPAR_REFACTOR

cdef class PY_SCIP_BASESTAT:
LOWER = SCIP_BASESTAT_LOWER
BASIC = SCIP_BASESTAT_BASIC
UPPER = SCIP_BASESTAT_UPPER
ZERO = SCIP_BASESTAT_ZERO

cdef class PY_SCIP_PARAMEMPHASIS:
DEFAULT = SCIP_PARAMEMPHASIS_DEFAULT
CPSOLVER = SCIP_PARAMEMPHASIS_CPSOLVER
Expand Down Expand Up @@ -316,7 +322,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 325 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / test-coverage (3.11)

SCIP: unspecified error!
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
Expand All @@ -335,7 +341,7 @@
raise Exception('SCIP: method cannot be called at this time'
+ ' in solution process!')
elif rc == SCIP_INVALIDDATA:
raise Exception('SCIP: error in input data!')

Check failure on line 344 in src/pyscipopt/scip.pxi

View workflow job for this annotation

GitHub Actions / test-coverage (3.11)

SCIP: error in input data!
elif rc == SCIP_INVALIDRESULT:
raise Exception('SCIP: method returned an invalid result code!')
elif rc == SCIP_PLUGINNOTFOUND:
Expand Down
9 changes: 9 additions & 0 deletions src/pyscipopt/scip.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ class LP:
def delCols(self, firstcol: Incomplete, lastcol: Incomplete) -> Incomplete: ...
def delRows(self, firstrow: Incomplete, lastrow: Incomplete) -> Incomplete: ...
def getActivity(self) -> Incomplete: ...
def getBase(self) -> Incomplete: ...
def getBasisInds(self) -> Incomplete: ...
def getBounds(
self, firstcol: Incomplete = ..., lastcol: Incomplete = ...
Expand All @@ -491,6 +492,7 @@ class LP:
def ncols(self) -> Incomplete: ...
def nrows(self) -> Incomplete: ...
def readLP(self, filename: Incomplete) -> Incomplete: ...
def setBase(self, cstat: Incomplete, rstat: Incomplete) -> Incomplete: ...
def setIntParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ...
def setRealParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ...
def solve(self, dual: Incomplete = ...) -> Incomplete: ...
Expand Down Expand Up @@ -1801,6 +1803,13 @@ class PY_SCIP_LOCKTYPE:
MODEL: ClassVar[int] = ...
def __init__(self) -> None: ...

class PY_SCIP_BASESTAT:
LOWER: ClassVar[int] = ...
BASIC: ClassVar[int] = ...
UPPER: ClassVar[int] = ...
ZERO: ClassVar[int] = ...
def __init__(self) -> None: ...

class PY_SCIP_LPPARAM:
BARRIERCONVTOL: ClassVar[int] = ...
CONDITIONLIMIT: ClassVar[int] = ...
Expand Down
23 changes: 23 additions & 0 deletions tests/test_lp.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pyscipopt import LP
from pyscipopt import SCIP_LPPARAM
from pyscipopt import SCIP_BASESTAT

def test_lp():
# create LP instance, minimizing by default
Expand Down Expand Up @@ -89,3 +90,25 @@ def test_lp():

assert round(myLP.getObjVal() == solval)
assert round(5.0 == solval)

# test basis get/set
binds = myLP.getBasisInds()
assert len(binds) == myLP.nrows()

cstat, rstat = myLP.getBase()
assert len(cstat) == myLP.ncols()
assert len(rstat) == myLP.nrows()
assert all(s in (SCIP_BASESTAT.LOWER, SCIP_BASESTAT.BASIC,
SCIP_BASESTAT.UPPER, SCIP_BASESTAT.ZERO) for s in cstat)
assert all(s in (SCIP_BASESTAT.LOWER, SCIP_BASESTAT.BASIC,
SCIP_BASESTAT.UPPER) for s in rstat)

# set the same basis back and re-solve
myLP.setBase(cstat, rstat)
solval2 = myLP.solve()
assert round(solval2, 10) == round(solval, 10)

# verify basis is preserved after set
cstat2, rstat2 = myLP.getBase()
assert cstat2 == cstat
assert rstat2 == rstat
Loading