Skip to content

Commit b145c78

Browse files
Merge branch 'master' into expr/__mul__
2 parents 4f200bf + 75ccba9 commit b145c78

7 files changed

Lines changed: 103 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
## Unreleased
44
### Added
5+
- Added `getBase()` and `setBase()` methods to `LP` class for getting/setting basis status
56
- Added `getMemUsed()`, `getMemTotal()`, and `getMemExternEstim()` methods
67
### Fixed
78
- Removed `Py_INCREF`/`Py_DECREF` on `Model` in `catchEvent`/`dropEvent` that caused memory leak for imbalanced usage
8-
- Used getIndex() instead of ptr() for sorting nonlinear expression terms to avoid nondeterministic behavior
9+
- Used `getIndex()` instead of `ptr()` for sorting nonlinear expression terms to avoid nondeterministic behavior
910
### Changed
1011
- Speed up `constant * Expr` via C-level API
1112
### Removed

src/pyscipopt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from pyscipopt.scip import LP as LP
2929
from pyscipopt.scip import IISfinder as IISfinder
3030
from pyscipopt.scip import PY_SCIP_LPPARAM as SCIP_LPPARAM
31+
from pyscipopt.scip import PY_SCIP_BASESTAT as SCIP_BASESTAT
3132
from pyscipopt.scip import readStatistics as readStatistics
3233
from pyscipopt.scip import Expr as Expr
3334
from pyscipopt.scip import MatrixExpr as MatrixExpr

src/pyscipopt/lp.pxi

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,66 @@ cdef class LP:
531531

532532
return binds
533533

534+
def getBase(self):
535+
"""Returns the basis status of columns and rows.
536+
537+
Status values are defined in SCIP_BASESTAT: LOWER, BASIC, UPPER, ZERO.
538+
539+
Returns
540+
-------
541+
tuple of (list of int, list of int)
542+
Column basis statuses and row basis statuses.
543+
544+
"""
545+
cdef int ncols = self.ncols()
546+
cdef int nrows = self.nrows()
547+
cdef int* c_cstat = <int*> malloc(ncols * sizeof(int))
548+
cdef int* c_rstat = <int*> malloc(nrows * sizeof(int))
549+
cdef int i
550+
551+
PY_SCIP_CALL(SCIPlpiGetBase(self.lpi, c_cstat, c_rstat))
552+
553+
cstat = [c_cstat[i] for i in range(ncols)]
554+
rstat = [c_rstat[i] for i in range(nrows)]
555+
556+
free(c_rstat)
557+
free(c_cstat)
558+
559+
return cstat, rstat
560+
561+
def setBase(self, cstat, rstat):
562+
"""Sets the basis status of columns and rows.
563+
564+
Status values are defined in SCIP_BASESTAT: LOWER, BASIC, UPPER, ZERO.
565+
566+
Parameters
567+
----------
568+
cstat : list of int
569+
Column basis statuses (length must equal ncols).
570+
rstat : list of int
571+
Row basis statuses (length must equal nrows).
572+
573+
"""
574+
cdef int ncols = self.ncols()
575+
cdef int nrows = self.nrows()
576+
if len(cstat) != ncols:
577+
raise ValueError(f"cstat has length {len(cstat)}, expected {ncols}")
578+
if len(rstat) != nrows:
579+
raise ValueError(f"rstat has length {len(rstat)}, expected {nrows}")
580+
cdef int* c_cstat = <int*> malloc(ncols * sizeof(int))
581+
cdef int* c_rstat = <int*> malloc(nrows * sizeof(int))
582+
cdef int i
583+
584+
for i in range(ncols):
585+
c_cstat[i] = cstat[i]
586+
for i in range(nrows):
587+
c_rstat[i] = rstat[i]
588+
589+
PY_SCIP_CALL(SCIPlpiSetBase(self.lpi, c_cstat, c_rstat))
590+
591+
free(c_rstat)
592+
free(c_cstat)
593+
534594
# Parameter Methods
535595

536596
def setIntParam(self, param, value):

src/pyscipopt/scip.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,8 @@ cdef extern from "scip/scip.h":
15321532
SCIP_RETCODE SCIPlpiGetPrimalRay(SCIP_LPI* lpi, SCIP_Real* ray)
15331533
SCIP_RETCODE SCIPlpiGetDualfarkas(SCIP_LPI* lpi, SCIP_Real* dualfarkas)
15341534
SCIP_RETCODE SCIPlpiGetBasisInd(SCIP_LPI* lpi, int* bind)
1535+
SCIP_RETCODE SCIPlpiGetBase(SCIP_LPI* lpi, int* cstat, int* rstat)
1536+
SCIP_RETCODE SCIPlpiSetBase(SCIP_LPI* lpi, const int* cstat, const int* rstat)
15351537
SCIP_RETCODE SCIPlpiGetRealSolQuality(SCIP_LPI* lpi, SCIP_LPSOLQUALITY qualityindicator, SCIP_Real* quality)
15361538
SCIP_RETCODE SCIPlpiGetIntpar(SCIP_LPI* lpi, SCIP_LPPARAM type, int* ival)
15371539
SCIP_RETCODE SCIPlpiGetRealpar(SCIP_LPI* lpi, SCIP_LPPARAM type, SCIP_Real* dval)

src/pyscipopt/scip.pxi

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ cdef class PY_SCIP_LPPARAM:
118118
POLISHING = SCIP_LPPAR_POLISHING
119119
REFACTOR = SCIP_LPPAR_REFACTOR
120120

121+
cdef class PY_SCIP_BASESTAT:
122+
LOWER = SCIP_BASESTAT_LOWER
123+
BASIC = SCIP_BASESTAT_BASIC
124+
UPPER = SCIP_BASESTAT_UPPER
125+
ZERO = SCIP_BASESTAT_ZERO
126+
121127
cdef class PY_SCIP_PARAMEMPHASIS:
122128
DEFAULT = SCIP_PARAMEMPHASIS_DEFAULT
123129
CPSOLVER = SCIP_PARAMEMPHASIS_CPSOLVER

src/pyscipopt/scip.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class LP:
467467
def delCols(self, firstcol: Incomplete, lastcol: Incomplete) -> Incomplete: ...
468468
def delRows(self, firstrow: Incomplete, lastrow: Incomplete) -> Incomplete: ...
469469
def getActivity(self) -> Incomplete: ...
470+
def getBase(self) -> Incomplete: ...
470471
def getBasisInds(self) -> Incomplete: ...
471472
def getBounds(
472473
self, firstcol: Incomplete = ..., lastcol: Incomplete = ...
@@ -491,6 +492,7 @@ class LP:
491492
def ncols(self) -> Incomplete: ...
492493
def nrows(self) -> Incomplete: ...
493494
def readLP(self, filename: Incomplete) -> Incomplete: ...
495+
def setBase(self, cstat: Incomplete, rstat: Incomplete) -> Incomplete: ...
494496
def setIntParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ...
495497
def setRealParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ...
496498
def solve(self, dual: Incomplete = ...) -> Incomplete: ...
@@ -1801,6 +1803,13 @@ class PY_SCIP_LOCKTYPE:
18011803
MODEL: ClassVar[int] = ...
18021804
def __init__(self) -> None: ...
18031805

1806+
class PY_SCIP_BASESTAT:
1807+
LOWER: ClassVar[int] = ...
1808+
BASIC: ClassVar[int] = ...
1809+
UPPER: ClassVar[int] = ...
1810+
ZERO: ClassVar[int] = ...
1811+
def __init__(self) -> None: ...
1812+
18041813
class PY_SCIP_LPPARAM:
18051814
BARRIERCONVTOL: ClassVar[int] = ...
18061815
CONDITIONLIMIT: ClassVar[int] = ...

tests/test_lp.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pyscipopt import LP
22
from pyscipopt import SCIP_LPPARAM
3+
from pyscipopt import SCIP_BASESTAT
34

45
def test_lp():
56
# create LP instance, minimizing by default
@@ -89,3 +90,25 @@ def test_lp():
8990

9091
assert round(myLP.getObjVal() == solval)
9192
assert round(5.0 == solval)
93+
94+
# test basis get/set
95+
binds = myLP.getBasisInds()
96+
assert len(binds) == myLP.nrows()
97+
98+
cstat, rstat = myLP.getBase()
99+
assert len(cstat) == myLP.ncols()
100+
assert len(rstat) == myLP.nrows()
101+
assert all(s in (SCIP_BASESTAT.LOWER, SCIP_BASESTAT.BASIC,
102+
SCIP_BASESTAT.UPPER, SCIP_BASESTAT.ZERO) for s in cstat)
103+
assert all(s in (SCIP_BASESTAT.LOWER, SCIP_BASESTAT.BASIC,
104+
SCIP_BASESTAT.UPPER) for s in rstat)
105+
106+
# set the same basis back and re-solve
107+
myLP.setBase(cstat, rstat)
108+
solval2 = myLP.solve()
109+
assert round(solval2, 10) == round(solval, 10)
110+
111+
# verify basis is preserved after set
112+
cstat2, rstat2 = myLP.getBase()
113+
assert cstat2 == cstat
114+
assert rstat2 == rstat

0 commit comments

Comments
 (0)