-
-
Notifications
You must be signed in to change notification settings - Fork 479
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
Add package pyscipopt, add MIP backend #21003
Comments
This comment has been minimized.
This comment has been minimized.
Branch: u/mkoeppe/pyscipopt |
Commit: |
Branch pushed to git repo; I updated commit sha1. New commits:
|
Branch pushed to git repo; I updated commit sha1. New commits:
|
This comment has been minimized.
This comment has been minimized.
Changed keywords from none to days84 |
Author: Matthias Koeppe, needs more authors |
This comment has been minimized.
This comment has been minimized.
Branch pushed to git repo; I updated commit sha1. This was a forced push. New commits:
|
Changed branch from u/mkoeppe/pyscipopt to u/moritz/pyscipopt |
comment:132
I hope that this is not too off topic here - please let me provide background on why I am copying MILPS: In #33238, I need to solve a program with temporarily added constraints. In principle, I see two options for doing this:
Currently, I am doing 1., because I am afraid that I cannot control what happens when adding a constraint. In particular, a clever solver might detect that it is redundant, or an even more intelligent solver might rewrite the whole list of constraints. Possibly I could check whether my new constraint wasn't added at all, but could a complete rewrite of a program happen in theory? |
comment:133
Replying to Martin Rubey:
Removing constraints is supported by our frontend and backend interfaces and by the SCIP API.
There's no need to worry about this. Working with dynamically modified models is a very typical operation in IP solvers, that includes delete constraints. See |
comment:134
Are you saying that it is very hard to imagine a solver that rewrites the constraints upon solving? I would prefer that my code works generically, and not just specifically for SCIP. Also, I don't really have any means to identify a constraint, except by index. I admit it may be silly, but in principle I do not even know whether my solver adds a constraint at the beginning or at the end. |
comment:135
Solvers do this kind of rewriting internally but do not expose it to the user. The API functions are there to be actually used. |
comment:136
Replying to Martin Rubey:
Yes, that's a valid concern. Our MIP frontend and backend interfaces are unfortunately underdeveloped and underdocumented. While the backend method |
comment:137
That said, it's OK to assume that the constraint is added at the end. |
comment:138
different solvers do it differently. Some are doing some kind of postprocessing, and so the problem as held by the backend might be different (but equivalent) |
comment:139
Here is a patch. With this patch SCIP does the problem in roughly 2/3 of the time, which is quite OK. I don't have gurobi installed right now. Could you please check and commit it - provided it is OK - on my behalf? diff --git a/src/sage/numerical/backends/scip_backend.pxd b/src/sage/numerical/backends/scip_backend.pxd
index b919fe4c223..dc4981a89c3 100644
--- a/src/sage/numerical/backends/scip_backend.pxd
+++ b/src/sage/numerical/backends/scip_backend.pxd
@@ -14,6 +14,7 @@ cdef class SCIPBackend(GenericBackend):
cdef model
cdef object variables
+ cdef object constraints
cpdef _get_model(self)
cpdef get_row_prim(self, int i)
diff --git a/src/sage/numerical/backends/scip_backend.pyx b/src/sage/numerical/backends/scip_backend.pyx
index d063d012b98..b2ecf0f923b 100644
--- a/src/sage/numerical/backends/scip_backend.pyx
+++ b/src/sage/numerical/backends/scip_backend.pyx
@@ -60,6 +60,16 @@ cdef class SCIPBackend(GenericBackend):
self.obj_constant_term = 0.0
self.variables = []
self.model.hideOutput()
+ # always set this to None if the list of constraints may change
+ self.constraints = None
+
+ def get_constraints(self):
+ """
+ Get all constraints of the problem.
+ """
+ if self.constraints is None:
+ self.constraints = self.model.getConss()
+ return self.constraints
cpdef _get_model(self):
"""
@@ -360,7 +370,9 @@ cdef class SCIPBackend(GenericBackend):
raise ValueError("The constraint's index i must satisfy 0 <= i < number_of_constraints")
if self.model.getStatus() != 'unknown':
self.model.freeTransform()
- self.model.delCons(self.model.getConss()[i])
+ self.constraints = None
+ self.model.delCons(self.get_constraints()[i])
+ self.constraints = None
cpdef add_linear_constraint(self, coefficients, lower_bound, upper_bound, name=None):
"""
@@ -408,6 +420,7 @@ cdef class SCIPBackend(GenericBackend):
cons = lower_bound <= (linfun <= upper_bound)
self.model.addCons(cons, name=name)
+ self.constraints = None
cpdef row(self, int index):
r"""
@@ -438,7 +451,7 @@ cdef class SCIPBackend(GenericBackend):
(2.0, 2.0)
"""
namedvars = [var.name for var in self.variables]
- valslinear = self.model.getValsLinear(self.model.getConss()[index])
+ valslinear = self.model.getValsLinear(self.get_constraints()[index])
cdef list indices = []
cdef list values = []
for var, coeff in valslinear.iteritems():
@@ -470,7 +483,7 @@ cdef class SCIPBackend(GenericBackend):
sage: p.row_bounds(0)
(2.0, 2.0)
"""
- cons = self.model.getConss()[index]
+ cons = self.get_constraints()[index]
lhs = self.model.getLhs(cons)
rhs = self.model.getRhs(cons)
if lhs == -self.model.infinity():
@@ -548,7 +561,8 @@ cdef class SCIPBackend(GenericBackend):
sage: p.nrows()
5
"""
- mcons = self.model.getConss()
+ mcons = self.get_constraints()
+ self.constraints = None # is this necessary?
# after update of
index = self.add_variable(lower_bound=-self.model.infinity())
var = self.variables[index]
@@ -770,7 +784,7 @@ cdef class SCIPBackend(GenericBackend):
sage: lp.get_row_prim(2)
8.0
"""
- return self.model.getActivity(self.model.getConss()[i])
+ return self.model.getActivity(self.get_constraints()[i])
cpdef int ncols(self):
"""
@@ -803,7 +817,7 @@ cdef class SCIPBackend(GenericBackend):
sage: p.nrows()
2
"""
- return len(self.model.getConss())
+ return len(self.get_constraints())
cpdef col_name(self, int index):
"""
@@ -840,7 +854,7 @@ cdef class SCIPBackend(GenericBackend):
sage: p.row_name(0)
'Empty constraint 1'
"""
- return self.model.getConss()[index].name
+ return self.get_constraints()[index].name
cpdef bint is_variable_binary(self, int index):
"""
@@ -1148,7 +1162,7 @@ cdef class SCIPBackend(GenericBackend):
cdef SCIPBackend cp = type(self)(maximization=self.is_maximization())
cp.problem_name(self.problem_name())
for i, v in enumerate(self.variables):
- vtype = v.vtype
+ vtype = v.vtype()
cp.add_variable(self.variable_lower_bound(i),
self.variable_upper_bound(i),
binary=vtype == 'BINARY',
@@ -1159,10 +1173,13 @@ cdef class SCIPBackend(GenericBackend):
assert self.ncols() == cp.ncols()
for i in range(self.nrows()):
- cp.add_linear_constraint(zip(*self.row(i)),
- self.row_bounds(i)[0],
- self.row_bounds(i)[1],
- name=self.row_name(i))
+ coefficients = zip(*self.row(i))
+ lower_bound, upper_bound = self.row_bounds(i)
+ name = self.row_name(i)
+ cp.add_linear_constraint(coefficients,
+ lower_bound,
+ upper_bound,
+ name=name)
assert self.nrows() == cp.nrows()
return cp |
comment:140
It might indeed be faster to add and remove constraints, because I am doing a lot of this. However, comment:138 seems to contradict comment:137 and comment:135. So maybe we could have some kind of flag telling us whether a solver does rewrite constraints or not? |
comment:141
I recommend that in your code you wrap your use of |
comment:142
Also, |
comment:143
Well, there is another thing with adding and removing: removing will - with the patch above - unnecessarily recompute the list of constraints again. If I additionally check that adding the constraint also increases the number of constraints by one, then I have this overhead twice. It's a bit strange that the backend needs the constraint to remove it, whereas in sage we need the index. That said, replacing copy with adding/removing constraints makes a HUGE difference for this problem, roughly a factor of 8! I won't be able to improve |
comment:144
Replying to Matthias Köppe:
I've opened #34878 for an API improvement |
comment:145
I tried to understand why
is there a good reason for that? |
comment:146
Either SCIP itself or PySCIPOpt is inventing these variable names, I think |
comment:147
Indeed def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False):
...
# replace empty name with generic one
if name == '':
name = 'x'+str(SCIPgetNVars(self._scip)+1) |
Branch pushed to git repo; I updated commit sha1. New commits:
|
Changed author from Matthias Koeppe, Moritz Firsching to Matthias Koeppe, Moritz Firsching, Martin Rubey |
Changed reviewer from Moritz Firsching, Vincent Delecroix, David Coudert to Moritz Firsching, Vincent Delecroix, David Coudert, Matthias Koeppe |
comment:151
Replying to Matthias Köppe:
That's now #34890 |
Changed branch from u/mkoeppe/pyscipopt to |
This ticket adds a package
pyscipopt
and adds a new MIP backend based on it.https://github.com/scipopt/PySCIPOpt
Branch is on top of #31329.
Steps to get it to work:
sage -f pyscipopt
Depends on #31329
CC: @mo271 @yuan-zhou @malb @dcoudert @dimpase
Component: linear programming
Keywords: days84, IMA-PolyGeom
Author: Matthias Koeppe, Moritz Firsching, Martin Rubey
Branch/Commit:
099c15b
Reviewer: Moritz Firsching, Vincent Delecroix, David Coudert, Matthias Koeppe
Issue created by migration from https://trac.sagemath.org/ticket/21003
The text was updated successfully, but these errors were encountered: