Skip to content

Add feastol numerics checks #997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## Unreleased
### Added
- Added support for knapsack constraints
- Added isPositive(), isNegative(), isFeasLE(), isFeasLT(), isFeasGE(), isFeasGT(), isHugeValue(), and tests
- Added SCIP_LOCKTYPE, addVarLocksType
### Fixed
### Changed
### Removed
Expand Down
78 changes: 44 additions & 34 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ cdef extern from "scip/scip.h":
SCIP_BENDERSENFOTYPE SCIP_BENDERSENFOTYPE_PSEUDO
SCIP_BENDERSENFOTYPE SCIP_BENDERSENFOTYPE_CHECK

ctypedef int SCIP_LOCKTYPE
cdef extern from "scip/type_var.h":
SCIP_LOCKTYPE SCIP_LOCKTYPE_MODEL
SCIP_LOCKTYPE SCIP_LOCKTYPE_CONFLICT

ctypedef int SCIP_LPSOLSTAT
cdef extern from "scip/type_lp.h":
SCIP_LPSOLSTAT SCIP_LPSOLSTAT_NOTSOLVED
Expand Down Expand Up @@ -541,9 +546,9 @@ cdef extern from "scip/scip.h":
ctypedef union SCIP_DOMCHG:
pass

ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR *messagehdlr, FILE *file, const char *msg) noexcept
ctypedef void (*errormessagecallback) (void *data, FILE *file, const char *msg)
ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR *messagehdlr)
ctypedef void (*messagecallback) (SCIP_MESSAGEHDLR* messagehdlr, FILE* file, const char* msg) noexcept
ctypedef void (*errormessagecallback) (void* data, FILE* file, const char* msg)
ctypedef SCIP_RETCODE (*messagehdlrfree) (SCIP_MESSAGEHDLR* messagehdlr)

# General SCIP Methods
SCIP_RETCODE SCIPcreate(SCIP** scip)
Expand All @@ -569,15 +574,15 @@ cdef extern from "scip/scip.h":
SCIP_Bool* valid)
SCIP_RETCODE SCIPcopyOrigVars(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_VAR** fixedvars, SCIP_Real* fixedvals, int nfixedvars )
SCIP_RETCODE SCIPcopyOrigConss(SCIP* sourcescip, SCIP* targetscip, SCIP_HASHMAP* varmap, SCIP_HASHMAP* consmap, SCIP_Bool enablepricing, SCIP_Bool* valid)
SCIP_RETCODE SCIPmessagehdlrCreate(SCIP_MESSAGEHDLR **messagehdlr,
SCIP_RETCODE SCIPmessagehdlrCreate(SCIP_MESSAGEHDLR** messagehdlr,
SCIP_Bool bufferedoutput,
const char *filename,
const char* filename,
SCIP_Bool quiet,
messagecallback,
messagecallback,
messagecallback,
messagehdlrfree,
SCIP_MESSAGEHDLRDATA *messagehdlrdata)
SCIP_MESSAGEHDLRDATA* messagehdlrdata)

SCIP_RETCODE SCIPsetMessagehdlr(SCIP* scip, SCIP_MESSAGEHDLR* messagehdlr)
void SCIPsetMessagehdlrQuiet(SCIP* scip, SCIP_Bool quiet)
Expand Down Expand Up @@ -787,6 +792,7 @@ cdef extern from "scip/scip.h":
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)
SCIP_RETCODE SCIPaddVarLocksType(SCIP* scip, SCIP_VAR* var, SCIP_LOCKTYPE locktype, int nlocksdown, int nlocksup)
SCIP_VAR** SCIPgetVars(SCIP* scip)
SCIP_VAR** SCIPgetOrigVars(SCIP* scip)
const char* SCIPvarGetName(SCIP_VAR* var)
Expand All @@ -813,11 +819,11 @@ cdef extern from "scip/scip.h":
void SCIPvarSetData(SCIP_VAR* var, SCIP_VARDATA* vardata)
SCIP_VARDATA* SCIPvarGetData(SCIP_VAR* var)
SCIP_Real SCIPvarGetAvgSol(SCIP_VAR* var)
SCIP_Real SCIPgetVarPseudocost(SCIP* scip, SCIP_VAR *var, SCIP_BRANCHDIR dir)
SCIP_Real SCIPgetVarPseudocost(SCIP* scip, SCIP_VAR* var, SCIP_BRANCHDIR dir)
SCIP_Real SCIPvarGetCutoffSum(SCIP_VAR* var, SCIP_BRANCHDIR dir)
SCIP_Longint SCIPvarGetNBranchings(SCIP_VAR* var, SCIP_BRANCHDIR dir)
SCIP_Bool SCIPvarMayRoundUp(SCIP_VAR* var)
SCIP_Bool SCIPvarMayRoundDown(SCIP_VAR * var)
SCIP_Bool SCIPvarMayRoundDown(SCIP_VAR* var)

# LP Methods
SCIP_RETCODE SCIPgetLPColsData(SCIP* scip, SCIP_COL*** cols, int* ncols)
Expand All @@ -831,8 +837,8 @@ cdef extern from "scip/scip.h":
SCIP_LPSOLSTAT SCIPgetLPSolstat(SCIP* scip)
int SCIPgetNLPRows(SCIP* scip)
int SCIPgetNLPCols(SCIP* scip)
SCIP_COL** SCIPgetLPCols(SCIP *scip)
SCIP_ROW** SCIPgetLPRows(SCIP *scip)
SCIP_COL** SCIPgetLPCols(SCIP* scip)
SCIP_ROW** SCIPgetLPRows(SCIP* scip)
SCIP_Bool SCIPallColsInLP(SCIP* scip)

# Cutting Plane Methods
Expand Down Expand Up @@ -872,11 +878,11 @@ cdef extern from "scip/scip.h":
const char* SCIPconshdlrGetName(SCIP_CONSHDLR* conshdlr)
SCIP_RETCODE SCIPdelConsLocal(SCIP* scip, SCIP_CONS* cons)
SCIP_RETCODE SCIPdelCons(SCIP* scip, SCIP_CONS* cons)
SCIP_RETCODE SCIPsetConsChecked(SCIP *scip, SCIP_CONS *cons, SCIP_Bool check)
SCIP_RETCODE SCIPsetConsRemovable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool removable)
SCIP_RETCODE SCIPsetConsInitial(SCIP *scip, SCIP_CONS *cons, SCIP_Bool initial)
SCIP_RETCODE SCIPsetConsModifiable(SCIP *scip, SCIP_CONS *cons, SCIP_Bool modifiable)
SCIP_RETCODE SCIPsetConsEnforced(SCIP *scip, SCIP_CONS *cons, SCIP_Bool enforce)
SCIP_RETCODE SCIPsetConsChecked(SCIP* scip, SCIP_CONS* cons, SCIP_Bool check)
SCIP_RETCODE SCIPsetConsRemovable(SCIP* scip, SCIP_CONS* cons, SCIP_Bool removable)
SCIP_RETCODE SCIPsetConsInitial(SCIP* scip, SCIP_CONS* cons, SCIP_Bool initial)
SCIP_RETCODE SCIPsetConsModifiable(SCIP* scip, SCIP_CONS* cons, SCIP_Bool modifiable)
SCIP_RETCODE SCIPsetConsEnforced(SCIP* scip, SCIP_CONS* cons, SCIP_Bool enforce)

# Primal Solution Methods
SCIP_SOL** SCIPgetSols(SCIP* scip)
Expand Down Expand Up @@ -906,8 +912,8 @@ cdef extern from "scip/scip.h":
SCIP_Real SCIPgetGap(SCIP* scip)
int SCIPgetDepth(SCIP* scip)
SCIP_RETCODE SCIPcutoffNode(SCIP* scip, SCIP_NODE* node)
SCIP_Bool SCIPhasPrimalRay(SCIP * scip)
SCIP_Real SCIPgetPrimalRayVal(SCIP * scip, SCIP_VAR * var)
SCIP_Bool SCIPhasPrimalRay(SCIP* scip)
SCIP_Real SCIPgetPrimalRayVal(SCIP* scip, SCIP_VAR* var)
SCIP_RETCODE SCIPaddSolFree(SCIP* scip, SCIP_SOL** sol, SCIP_Bool* stored)
SCIP_RETCODE SCIPaddSol(SCIP* scip, SCIP_SOL* sol, SCIP_Bool* stored)
SCIP_RETCODE SCIPreadSol(SCIP* scip, const char* filename)
Expand Down Expand Up @@ -1051,7 +1057,7 @@ cdef extern from "scip/scip.h":
SCIP_RETCODE (*consgetnvars) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, int* nvars, SCIP_Bool* success),
SCIP_RETCODE (*consgetdivebdchgs) (SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_DIVESET* diveset, SCIP_SOL* sol, SCIP_Bool* success, SCIP_Bool* infeasible),
SCIP_RETCODE (*consgetpermsymgraph)(SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SYM_GRAPH* graph, SCIP_Bool* success),
SCIP_RETCODE (*consgetsignedpermsymgraph)(SCIP * scip, SCIP_CONSHDLR * conshdlr, SCIP_CONS * cons, SYM_GRAPH * graph, SCIP_Bool * success),
SCIP_RETCODE (*consgetsignedpermsymgraph)(SCIP* scip, SCIP_CONSHDLR* conshdlr, SCIP_CONS* cons, SYM_GRAPH* graph, SCIP_Bool* success),
SCIP_CONSHDLRDATA* conshdlrdata)
SCIP_CONSHDLRDATA* SCIPconshdlrGetData(SCIP_CONSHDLR* conshdlr)
SCIP_CONSHDLR* SCIPfindConshdlr(SCIP* scip, const char* name)
Expand Down Expand Up @@ -1339,13 +1345,17 @@ cdef extern from "scip/scip.h":
SCIP_Bool SCIPisLT(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisGE(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisGT(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisEQ(SCIP *scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasEQ(SCIP *scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisHugeValue(SCIP *scip, SCIP_Real val)
SCIP_Bool SCIPisPositive(SCIP *scip, SCIP_Real val)
SCIP_Bool SCIPisNegative(SCIP *scip, SCIP_Real val)
SCIP_Bool SCIPisIntegral(SCIP *scip, SCIP_Real val)
SCIP_Real SCIPgetTreesizeEstimation(SCIP *scip)
SCIP_Bool SCIPisEQ(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasEQ(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasLT(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasGT(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasLE(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisFeasGE(SCIP* scip, SCIP_Real val1, SCIP_Real val2)
SCIP_Bool SCIPisHugeValue(SCIP* scip, SCIP_Real val)
SCIP_Bool SCIPisPositive(SCIP* scip, SCIP_Real val)
SCIP_Bool SCIPisNegative(SCIP* scip, SCIP_Real val)
SCIP_Bool SCIPisIntegral(SCIP* scip, SCIP_Real val)
SCIP_Real SCIPgetTreesizeEstimation(SCIP* scip)

# Statistic Methods
SCIP_RETCODE SCIPprintStatistics(SCIP* scip, FILE* outfile)
Expand Down Expand Up @@ -1611,22 +1621,22 @@ cdef extern from "scip/cons_sos2.h":
SCIP_VAR* var)

cdef extern from "scip/cons_disjunction.h":
SCIP_RETCODE SCIPcreateConsDisjunction(SCIP *scip,
SCIP_CONS **cons,
const char *name,
SCIP_RETCODE SCIPcreateConsDisjunction(SCIP* scip,
SCIP_CONS** cons,
const char* name,
int nconss,
SCIP_CONS **conss,
SCIP_CONS *relaxcons,
SCIP_CONS** conss,
SCIP_CONS* relaxcons,
SCIP_Bool initial,
SCIP_Bool enforce,
SCIP_Bool check,
SCIP_Bool local,
SCIP_Bool modifiable,
SCIP_Bool dynamic)

SCIP_RETCODE SCIPaddConsElemDisjunction(SCIP *scip,
SCIP_CONS *cons,
SCIP_CONS *addcons)
SCIP_RETCODE SCIPaddConsElemDisjunction(SCIP* scip,
SCIP_CONS* cons,
SCIP_CONS* addcons)

cdef extern from "scip/cons_and.h":
SCIP_RETCODE SCIPcreateConsAnd(SCIP* scip,
Expand Down Expand Up @@ -1963,7 +1973,7 @@ cdef extern from "scip/pub_lp.h":
SCIP_Real* SCIPcolGetVals(SCIP_COL* col)
int SCIPcolGetAge(SCIP_COL* col)
int SCIPcolGetIndex(SCIP_COL* col)
SCIP_Real SCIPcolGetObj(SCIP_COL *col)
SCIP_Real SCIPcolGetObj(SCIP_COL* col)

cdef extern from "scip/scip_tree.h":
SCIP_RETCODE SCIPgetOpenNodesData(SCIP* scip, SCIP_NODE*** leaves, SCIP_NODE*** children, SCIP_NODE*** siblings, int* nleaves, int* nchildren, int* nsiblings)
Expand Down
89 changes: 87 additions & 2 deletions src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@
ROWCHANGED = SCIP_EVENTTYPE_ROWCHANGED
ROWEVENT = SCIP_EVENTTYPE_ROWEVENT

cdef class PY_SCIP_LOCKTYPE:
MODEL = SCIP_LOCKTYPE_MODEL
CONFLICT = SCIP_LOCKTYPE_CONFLICT

cdef class PY_SCIP_LPSOLSTAT:
NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED
Expand Down Expand Up @@ -298,7 +301,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 304 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 Down Expand Up @@ -3070,7 +3073,7 @@

def isFeasEQ(self, val1, val2):
"""
Checks, if relative difference of values is in range of feasibility tolerance.
Returns if |val1 - val2| <= feastol.

Parameters
----------
Expand All @@ -3083,6 +3086,70 @@

"""
return SCIPisFeasEQ(self._scip, val1, val2)

def isFeasLT(self, val1, val2):
"""
Returns if relative difference between val1 - val2 <= -feastol.

Parameters
----------
val1 : float
val2 : float

Returns
-------
bool

"""
return SCIPisFeasLT(self._scip, val1, val2)

def isFeasLE(self, val1, val2):
"""
Returns if val1 - val2 <= feastol.

Parameters
----------
val1 : float
val2 : float

Returns
-------
bool

"""
return SCIPisFeasLE(self._scip, val1, val2)

def isFeasGT(self, val1, val2):
"""
Returns if val1 - val2 > feastol.

Parameters
----------
val1 : float
val2 : float

Returns
-------
bool

"""
return SCIPisFeasGT(self._scip, val1, val2)

def isFeasGE(self, val1, val2):
"""
Returns if val1 - val2 >= -feastol.

Parameters
----------
val1 : float
val2 : float

Returns
-------
bool

"""
return SCIPisFeasGE(self._scip, val1, val2)

def isLE(self, val1, val2):
"""
Expand Down Expand Up @@ -3792,7 +3859,7 @@

return Variable.create(_tvar)

def addVarLocks(self, Variable var, nlocksdown, nlocksup):
def addVarLocks(self, Variable var, int nlocksdown, int nlocksup):
"""
Adds given values to lock numbers of variable for rounding.

Expand All @@ -3808,6 +3875,24 @@
"""
PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.scip_var, nlocksdown, nlocksup))

def addVarLocksType(self, Variable var, int locktype, int nlocksdown, int nlocksup):
"""
adds given values to lock numbers of type locktype of variable for rounding

Parameters
----------
var : Variable
variable to adjust the locks for
type : str
type of the variable locks
nlocksdown : int
modification in number of rounding down locks
nlocksup : int
modification in number of rounding up locks

"""
PY_SCIP_CALL(SCIPaddVarLocksType(self._scip, var.scip_var, locktype, nlocksdown, nlocksup))

def fixVar(self, Variable var, val):
"""
Fixes the variable var to the value val if possible.
Expand Down
18 changes: 10 additions & 8 deletions tests/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ def random_mip_1(disable_sepa=True, disable_heur=True, disable_presolve=True, no


def random_lp_1():
return random_mip_1().relax()
random_mip_1().relax()
return random_mip_1()


def random_nlp_1():
model = Model()

v = model.addVar()
w = model.addVar()
x = model.addVar()
y = model.addVar()
z = model.addVar()
v = model.addVar(name="v", ub=2)
w = model.addVar(name="w", ub=3)
x = model.addVar(name="x", ub=4)
y = model.addVar(name="y", ub=1.4)
z = model.addVar(name="z", ub=4)

model.addCons(exp(v) + log(w) + sqrt(x) + sin(y) + z ** 3 * y <= 5)
model.setObjective(v + w + x + y + z, sense='maximize')
Expand All @@ -87,7 +88,7 @@ def knapsack_model(weights=[4, 2, 6, 3, 7, 5], costs=[7, 2, 5, 4, 3, 4], knapsac
knapsackVars.append(s.addVar(varNames[i], vtype='I', obj=costs[i], ub=1.0))

# adding a linear constraint for the knapsack constraint
s.addCons(quicksum(w * v for (w, v) in zip(weights, knapsackVars)) <= knapsackSize)
s.addCons(quicksum(w * v for (w, v) in zip(weights, knapsackVars)) <= knapsack_size)

return s

Expand Down Expand Up @@ -249,4 +250,5 @@ def bin_packing_lp(sizes, capacity):


def gastrans_lp():
return gastrans_model().relax()
gastrans_model().relax()
return gastrans_model()
21 changes: 21 additions & 0 deletions tests/test_numerics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from pyscipopt import Model
import pytest

def test_numerical_checks():
m = Model()

m.setParam("numerics/epsilon", 1e-10)
m.setParam("numerics/feastol", 1e-3)

assert m.isFeasEQ(1, 1.00001)
assert not m.isEQ(1, 1.00001)

assert m.isFeasLE(1, 0.99999)
assert not m.isLE(1, 0.99999)

assert m.isFeasGE(1, 1.00001)
assert not m.isGE(1, 1.00001)

assert not m.isFeasGT(1, 0.99999)
assert m.isGT(1, 0.99999)