Skip to content

Commit 5544a33

Browse files
author
Release Manager
committed
Trac #21003: Add package pyscipopt, add MIP backend
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: - pull from this branch - `sage -f pyscipopt` URL: https://trac.sagemath.org/21003 Reported by: mkoeppe Ticket author(s): Matthias Koeppe, Moritz Firsching, Martin Rubey Reviewer(s): Moritz Firsching, Vincent Delecroix, David Coudert, Matthias Koeppe
2 parents b0422b2 + 099c15b commit 5544a33

File tree

14 files changed

+1424
-64
lines changed

14 files changed

+1424
-64
lines changed

build/pkgs/pyscipopt/SPKG.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
pyscipopt: Python interface and modeling environment for SCIP
2+
=============================================================
3+
4+
Description
5+
-----------
6+
7+
Python interface and modeling environment for SCIP
8+
9+
License
10+
-------
11+
12+
MIT
13+
14+
Upstream Contact
15+
----------------
16+
17+
https://pypi.org/project/PySCIPOpt/
18+
19+
Dependencies
20+
------------
21+
22+
scipoptsuite

build/pkgs/pyscipopt/checksums.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tarball=PySCIPOpt-VERSION.tar.gz
2+
sha1=0c3644ce6a0624774dceaef10694e090e3863ab5
3+
md5=2e4ce8087fb9acac8e806655f20c3d70
4+
cksum=3316817556
5+
upstream_url=https://pypi.io/packages/source/p/pyscipopt/PySCIPOpt-VERSION.tar.gz

build/pkgs/pyscipopt/dependencies

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
$(PYTHON) scip | $(PYTHON_TOOLCHAIN)
2+
3+
----------
4+
All lines of this file are ignored except the first.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pyscipopt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
math/py-PySCIPOpt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PySCIPOpt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4.2.0

build/pkgs/pyscipopt/spkg-install.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cd src
2+
export SCIPOPTDIR="$SAGE_LOCAL"
3+
sdh_pip_install .

build/pkgs/pyscipopt/type

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
optional

src/sage/graphs/graph_decompositions/vertex_separation.pyx

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def linear_ordering_to_path_decomposition(G, L):
474474
##################################################################
475475

476476
def pathwidth(self, k=None, certificate=False, algorithm="BAB", verbose=False,
477-
max_prefix_length=20, max_prefix_number=10**6):
477+
max_prefix_length=20, max_prefix_number=10**6, *, solver=None):
478478
r"""
479479
Compute the pathwidth of ``self`` (and provides a decomposition)
480480
@@ -513,6 +513,14 @@ def pathwidth(self, k=None, certificate=False, algorithm="BAB", verbose=False,
513513
number of stored prefixes used to prevent using too much memory. This
514514
parameter is used only when ``algorithm=="BAB"``.
515515
516+
- ``solver`` -- string (default: ``None``); specify a Mixed Integer Linear
517+
Programming (MILP) solver to be used. If set to ``None``, the default one
518+
is used. For more information on MILP solvers and which default solver is
519+
used, see the method :meth:`solve
520+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
521+
:class:`MixedIntegerLinearProgram
522+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
523+
516524
OUTPUT:
517525
518526
Return the pathwidth of ``self``. When ``k`` is specified, it returns
@@ -566,6 +574,12 @@ def pathwidth(self, k=None, certificate=False, algorithm="BAB", verbose=False,
566574
Traceback (most recent call last):
567575
...
568576
ValueError: algorithm "SuperFast" has not been implemented yet, please contribute
577+
578+
Using a specific solver::
579+
580+
sage: g = graphs.PetersenGraph()
581+
sage: g.pathwidth(solver='SCIP') # optional - pyscipopt
582+
5
569583
"""
570584
from sage.graphs.graph import Graph
571585
if not isinstance(self, Graph):
@@ -574,7 +588,8 @@ def pathwidth(self, k=None, certificate=False, algorithm="BAB", verbose=False,
574588
pw, L = vertex_separation(self, algorithm=algorithm, verbose=verbose,
575589
cut_off=k, upper_bound=None if k is None else (k+1),
576590
max_prefix_length=max_prefix_length,
577-
max_prefix_number=max_prefix_number)
591+
max_prefix_number=max_prefix_number,
592+
solver=solver)
578593

579594
if k is None:
580595
return (pw, linear_ordering_to_path_decomposition(self, L)) if certificate else pw
@@ -786,6 +801,13 @@ def vertex_separation(G, algorithm="BAB", cut_off=None, upper_bound=None, verbos
786801
sage: print(vertex_separation(D))
787802
(3, [10, 11, 8, 9, 4, 5, 6, 7, 0, 1, 2, 3])
788803
804+
Using a specific MILP solver::
805+
806+
sage: from sage.graphs.graph_decompositions.vertex_separation import vertex_separation
807+
sage: G = graphs.PetersenGraph()
808+
sage: vs, L = vertex_separation(G, algorithm="MILP", solver="SCIP"); vs # optional - pyscipopt
809+
5
810+
789811
TESTS:
790812
791813
Given a wrong algorithm::
@@ -1249,6 +1271,108 @@ def width_of_path_decomposition(G, L):
12491271
# MILP formulation for vertex separation #
12501272
##########################################
12511273

1274+
def _vertex_separation_MILP_formulation(G, integrality=False, solver=None):
1275+
r"""
1276+
MILP formulation of the vertex separation of `G` and the optimal ordering of its vertices.
1277+
1278+
This MILP is an improved version of the formulation proposed in [SP2010]_. See the
1279+
:mod:`module's documentation <sage.graphs.graph_decompositions.vertex_separation>` for
1280+
more details on this MILP formulation.
1281+
1282+
INPUT:
1283+
1284+
- ``G`` -- a Graph or a DiGraph
1285+
1286+
- ``integrality`` -- boolean (default: ``False``); specify if variables
1287+
`x_v^t` and `u_v^t` must be integral or if they can be relaxed. This has
1288+
no impact on the validity of the solution, but it is sometimes faster to
1289+
solve the problem using binary variables only.
1290+
1291+
- ``solver`` -- string (default: ``None``); specify a Mixed Integer Linear
1292+
Programming (MILP) solver to be used. If set to ``None``, the default one
1293+
is used. For more information on MILP solvers and which default solver is
1294+
used, see the method :meth:`solve
1295+
<sage.numerical.mip.MixedIntegerLinearProgram.solve>` of the class
1296+
:class:`MixedIntegerLinearProgram
1297+
<sage.numerical.mip.MixedIntegerLinearProgram>`.
1298+
1299+
OUTPUT:
1300+
1301+
- the :class:`~sage.numerical.mip.MixedIntegerLinearProgram`
1302+
1303+
- :class:`sage.numerical.mip.MIPVariable` objects ``x``, ``u``, ``y``, ``z``.
1304+
1305+
EXAMPLES::
1306+
1307+
sage: from sage.graphs.graph_decompositions.vertex_separation import _vertex_separation_MILP_formulation
1308+
sage: G = digraphs.DeBruijn(2,3)
1309+
sage: p, x, u, y, z = _vertex_separation_MILP_formulation(G)
1310+
sage: p
1311+
Mixed Integer Program (minimization, 193 variables, 449 constraints)
1312+
"""
1313+
from sage.graphs.graph import Graph
1314+
from sage.graphs.digraph import DiGraph
1315+
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
1316+
raise ValueError("the first input parameter must be a Graph or a DiGraph")
1317+
1318+
from sage.numerical.mip import MixedIntegerLinearProgram
1319+
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
1320+
1321+
# Declaration of variables.
1322+
x = p.new_variable(binary=integrality, nonnegative=True)
1323+
u = p.new_variable(binary=integrality, nonnegative=True)
1324+
y = p.new_variable(binary=True)
1325+
z = p.new_variable(integer=True, nonnegative=True)
1326+
1327+
N = G.order()
1328+
V = list(G)
1329+
neighbors_out = G.neighbors_out if G.is_directed() else G.neighbors
1330+
1331+
# (2) x[v,t] <= x[v,t+1] for all v in V, and for t:=0..N-2
1332+
# (3) y[v,t] <= y[v,t+1] for all v in V, and for t:=0..N-2
1333+
for v in V:
1334+
for t in range(N - 1):
1335+
p.add_constraint(x[v, t] - x[v, t + 1] <= 0)
1336+
p.add_constraint(y[v, t] - y[v, t + 1] <= 0)
1337+
1338+
# (4) y[v,t] <= x[w,t] for all v in V, for all w in N^+(v), and for all t:=0..N-1
1339+
for v in V:
1340+
for w in neighbors_out(v):
1341+
for t in range(N):
1342+
p.add_constraint(y[v, t] - x[w, t] <= 0)
1343+
1344+
# (5) sum_{v in V} y[v,t] == t+1 for t:=0..N-1
1345+
for t in range(N):
1346+
p.add_constraint(p.sum(y[v, t] for v in V) == t + 1)
1347+
1348+
# (6) u[v,t] >= x[v,t]-y[v,t] for all v in V, and for all t:=0..N-1
1349+
for v in V:
1350+
for t in range(N):
1351+
p.add_constraint(x[v, t] - y[v, t] - u[v, t] <= 0)
1352+
1353+
# (7) z >= sum_{v in V} u[v,t] for all t:=0..N-1
1354+
for t in range(N):
1355+
p.add_constraint(p.sum(u[v, t] for v in V) - z['z'] <= 0)
1356+
1357+
# (8)(9) 0 <= x[v,t] and u[v,t] <= 1
1358+
if not integrality:
1359+
for v in V:
1360+
for t in range(N):
1361+
p.add_constraint(x[v, t], min=0, max=1)
1362+
p.add_constraint(u[v, t], min=0, max=1)
1363+
1364+
# (10) y[v,t] in {0,1}
1365+
# already declared
1366+
1367+
# (11) 0 <= z <= |V|
1368+
p.add_constraint(z['z'] <= N)
1369+
1370+
# (1) Minimize z
1371+
p.set_objective(z['z'])
1372+
1373+
return p, x, u, y, z
1374+
1375+
12521376
@rename_keyword(deprecation=32222, verbosity='verbose')
12531377
def vertex_separation_MILP(G, integrality=False, solver=None, verbose=0,
12541378
*, integrality_tolerance=1e-3):
@@ -1341,65 +1465,11 @@ def vertex_separation_MILP(G, integrality=False, solver=None, verbose=0,
13411465
...
13421466
ValueError: the first input parameter must be a Graph or a DiGraph
13431467
"""
1344-
from sage.graphs.graph import Graph
1345-
from sage.graphs.digraph import DiGraph
1346-
if not isinstance(G, Graph) and not isinstance(G, DiGraph):
1347-
raise ValueError("the first input parameter must be a Graph or a DiGraph")
1348-
1349-
from sage.numerical.mip import MixedIntegerLinearProgram, MIPSolverException
1350-
p = MixedIntegerLinearProgram(maximization=False, solver=solver)
1351-
1352-
# Declaration of variables.
1353-
x = p.new_variable(binary=integrality, nonnegative=True)
1354-
u = p.new_variable(binary=integrality, nonnegative=True)
1355-
y = p.new_variable(binary=True)
1356-
z = p.new_variable(integer=True, nonnegative=True)
1468+
from sage.numerical.mip import MIPSolverException
13571469

1470+
p, x, u, y, z = _vertex_separation_MILP_formulation(G, integrality=integrality, solver=solver)
13581471
N = G.order()
13591472
V = list(G)
1360-
neighbors_out = G.neighbors_out if G.is_directed() else G.neighbors
1361-
1362-
# (2) x[v,t] <= x[v,t+1] for all v in V, and for t:=0..N-2
1363-
# (3) y[v,t] <= y[v,t+1] for all v in V, and for t:=0..N-2
1364-
for v in V:
1365-
for t in range(N - 1):
1366-
p.add_constraint(x[v, t] - x[v, t + 1] <= 0)
1367-
p.add_constraint(y[v, t] - y[v, t + 1] <= 0)
1368-
1369-
# (4) y[v,t] <= x[w,t] for all v in V, for all w in N^+(v), and for all t:=0..N-1
1370-
for v in V:
1371-
for w in neighbors_out(v):
1372-
for t in range(N):
1373-
p.add_constraint(y[v, t] - x[w, t] <= 0)
1374-
1375-
# (5) sum_{v in V} y[v,t] == t+1 for t:=0..N-1
1376-
for t in range(N):
1377-
p.add_constraint(p.sum(y[v, t] for v in V) == t + 1)
1378-
1379-
# (6) u[v,t] >= x[v,t]-y[v,t] for all v in V, and for all t:=0..N-1
1380-
for v in V:
1381-
for t in range(N):
1382-
p.add_constraint(x[v, t] - y[v, t] - u[v, t] <= 0)
1383-
1384-
# (7) z >= sum_{v in V} u[v,t] for all t:=0..N-1
1385-
for t in range(N):
1386-
p.add_constraint(p.sum(u[v, t] for v in V) - z['z'] <= 0)
1387-
1388-
# (8)(9) 0 <= x[v,t] and u[v,t] <= 1
1389-
if not integrality:
1390-
for v in V:
1391-
for t in range(N):
1392-
p.add_constraint(x[v, t], min=0, max=1)
1393-
p.add_constraint(u[v, t], min=0, max=1)
1394-
1395-
# (10) y[v,t] in {0,1}
1396-
# already declared
1397-
1398-
# (11) 0 <= z <= |V|
1399-
p.add_constraint(z['z'] <= N)
1400-
1401-
# (1) Minimize z
1402-
p.set_objective(z['z'])
14031473

14041474
try:
14051475
obj = p.solve(log=verbose)

0 commit comments

Comments
 (0)