Skip to content
Merged
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
- 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
60 changes: 60 additions & 0 deletions src/pyscipopt/lp.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,66 @@ 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)

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 = self.ncols()
cdef int nrows = self.nrows()
if len(cstat) != ncols:
raise ValueError(f"cstat has length {len(cstat)}, expected {ncols}")
if len(rstat) != nrows:
raise ValueError(f"rstat has length {len(rstat)}, expected {nrows}")
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))

free(c_rstat)
free(c_cstat)

# 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