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 4 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 @@ -3,6 +3,7 @@
## Unreleased
### Added
- Added support for knapsack constraints
- Added isFeasLE, isFeasLT, isFeasGE, isFeasGT, and tests
### Fixed
### Changed
### Removed
Expand Down
4 changes: 4 additions & 0 deletions src/pyscipopt/scip.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,10 @@ cdef extern from "scip/scip.h":
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 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)
Expand Down
66 changes: 65 additions & 1 deletion src/pyscipopt/scip.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')

Check failure on line 301 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 +3070,7 @@

def isFeasEQ(self, val1, val2):
"""
Checks, if relative difference of values is in range of feasibility tolerance.
Returns if relative difference of values is below feasibility tolerance.

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

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

def isFeasLT(self, val1, val2):
"""
Returns if relative difference between val1 and val2 is less than feasibility tolerance.

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

Returns
-------
bool

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

def isFeasLE(self, val1, val2):
"""
Returns if relative difference between val1 and val2 is less than or equal to feasibility tolerance.

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

Returns
-------
bool

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

def isFeasGT(self, val1, val2):
"""
Returns if relative difference between val1 and val2 is greater than feasibility tolerance.

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

Returns
-------
bool

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

def isFeasGE(self, val1, val2):
"""
Returns if relative difference between val1 and val2 is greater than or equal to feasibility tolerance.

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

Returns
-------
bool

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

def isLE(self, val1, val2):
"""
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()
20 changes: 20 additions & 0 deletions tests/test_numerics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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.9999)
assert m.isGT(1, 0.9999)