passagemath-polyhedra 10.6.37__cp314-cp314-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- passagemath_polyhedra/__init__.py +3 -0
- passagemath_polyhedra-10.6.37.dist-info/METADATA +367 -0
- passagemath_polyhedra-10.6.37.dist-info/METADATA.bak +369 -0
- passagemath_polyhedra-10.6.37.dist-info/RECORD +209 -0
- passagemath_polyhedra-10.6.37.dist-info/WHEEL +5 -0
- passagemath_polyhedra-10.6.37.dist-info/top_level.txt +3 -0
- passagemath_polyhedra.libs/libgcc_s-0cd532bd.so.1 +0 -0
- passagemath_polyhedra.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
- passagemath_polyhedra.libs/libgomp-8949ffbe.so.1.0.0 +0 -0
- passagemath_polyhedra.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
- sage/all__sagemath_polyhedra.py +50 -0
- sage/game_theory/all.py +8 -0
- sage/game_theory/catalog.py +6 -0
- sage/game_theory/catalog_normal_form_games.py +923 -0
- sage/game_theory/cooperative_game.py +844 -0
- sage/game_theory/matching_game.py +1181 -0
- sage/game_theory/normal_form_game.py +2697 -0
- sage/game_theory/parser.py +275 -0
- sage/geometry/all__sagemath_polyhedra.py +22 -0
- sage/geometry/cone.py +6940 -0
- sage/geometry/cone_catalog.py +847 -0
- sage/geometry/cone_critical_angles.py +1027 -0
- sage/geometry/convex_set.py +1119 -0
- sage/geometry/fan.py +3743 -0
- sage/geometry/fan_isomorphism.py +389 -0
- sage/geometry/fan_morphism.py +1884 -0
- sage/geometry/hasse_diagram.py +202 -0
- sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
- sage/geometry/hyperplane_arrangement/all.py +1 -0
- sage/geometry/hyperplane_arrangement/arrangement.py +3905 -0
- sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
- sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
- sage/geometry/hyperplane_arrangement/library.py +825 -0
- sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
- sage/geometry/hyperplane_arrangement/plot.py +520 -0
- sage/geometry/integral_points.py +35 -0
- sage/geometry/integral_points_generic_dense.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/integral_points_generic_dense.pyx +7 -0
- sage/geometry/lattice_polytope.py +5894 -0
- sage/geometry/linear_expression.py +773 -0
- sage/geometry/newton_polygon.py +767 -0
- sage/geometry/point_collection.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/point_collection.pyx +1008 -0
- sage/geometry/polyhedral_complex.py +2616 -0
- sage/geometry/polyhedron/all.py +8 -0
- sage/geometry/polyhedron/backend_cdd.py +460 -0
- sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
- sage/geometry/polyhedron/backend_field.py +347 -0
- sage/geometry/polyhedron/backend_normaliz.py +2503 -0
- sage/geometry/polyhedron/backend_number_field.py +168 -0
- sage/geometry/polyhedron/backend_polymake.py +765 -0
- sage/geometry/polyhedron/backend_ppl.py +582 -0
- sage/geometry/polyhedron/base.py +1206 -0
- sage/geometry/polyhedron/base0.py +1444 -0
- sage/geometry/polyhedron/base1.py +886 -0
- sage/geometry/polyhedron/base2.py +812 -0
- sage/geometry/polyhedron/base3.py +1845 -0
- sage/geometry/polyhedron/base4.py +1262 -0
- sage/geometry/polyhedron/base5.py +2700 -0
- sage/geometry/polyhedron/base6.py +1741 -0
- sage/geometry/polyhedron/base7.py +997 -0
- sage/geometry/polyhedron/base_QQ.py +1258 -0
- sage/geometry/polyhedron/base_RDF.py +98 -0
- sage/geometry/polyhedron/base_ZZ.py +934 -0
- sage/geometry/polyhedron/base_mutable.py +215 -0
- sage/geometry/polyhedron/base_number_field.py +122 -0
- sage/geometry/polyhedron/cdd_file_format.py +155 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
- sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
- sage/geometry/polyhedron/constructor.py +773 -0
- sage/geometry/polyhedron/double_description.py +753 -0
- sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
- sage/geometry/polyhedron/face.py +1060 -0
- sage/geometry/polyhedron/generating_function.py +1810 -0
- sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
- sage/geometry/polyhedron/library.py +3502 -0
- sage/geometry/polyhedron/misc.py +121 -0
- sage/geometry/polyhedron/modules/all.py +1 -0
- sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
- sage/geometry/polyhedron/palp_database.py +447 -0
- sage/geometry/polyhedron/parent.py +1279 -0
- sage/geometry/polyhedron/plot.py +1986 -0
- sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
- sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
- sage/geometry/polyhedron/representation.py +1723 -0
- sage/geometry/pseudolines.py +515 -0
- sage/geometry/relative_interior.py +445 -0
- sage/geometry/toric_plotter.py +1103 -0
- sage/geometry/triangulation/all.py +2 -0
- sage/geometry/triangulation/base.cpython-314-x86_64-linux-musl.so +0 -0
- sage/geometry/triangulation/base.pyx +963 -0
- sage/geometry/triangulation/data.h +147 -0
- sage/geometry/triangulation/data.pxd +4 -0
- sage/geometry/triangulation/element.py +914 -0
- sage/geometry/triangulation/functions.h +10 -0
- sage/geometry/triangulation/functions.pxd +4 -0
- sage/geometry/triangulation/point_configuration.py +2256 -0
- sage/geometry/triangulation/triangulations.h +49 -0
- sage/geometry/triangulation/triangulations.pxd +7 -0
- sage/geometry/voronoi_diagram.py +319 -0
- sage/interfaces/all__sagemath_polyhedra.py +1 -0
- sage/interfaces/polymake.py +2028 -0
- sage/numerical/all.py +13 -0
- sage/numerical/all__sagemath_polyhedra.py +11 -0
- sage/numerical/backends/all.py +1 -0
- sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
- sage/numerical/backends/cvxopt_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/cvxopt_backend.pyx +1006 -0
- sage/numerical/backends/cvxopt_backend_test.py +19 -0
- sage/numerical/backends/cvxopt_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
- sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/cvxpy_backend.pxd +41 -0
- sage/numerical/backends/cvxpy_backend.pyx +934 -0
- sage/numerical/backends/cvxpy_backend_test.py +13 -0
- sage/numerical/backends/generic_backend_test.py +24 -0
- sage/numerical/backends/interactivelp_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/interactivelp_backend.pxd +36 -0
- sage/numerical/backends/interactivelp_backend.pyx +1231 -0
- sage/numerical/backends/interactivelp_backend_test.py +12 -0
- sage/numerical/backends/logging_backend.py +391 -0
- sage/numerical/backends/matrix_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
- sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
- sage/numerical/backends/ppl_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/ppl_backend.pyx +1126 -0
- sage/numerical/backends/ppl_backend_test.py +13 -0
- sage/numerical/backends/scip_backend.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/backends/scip_backend.pxd +22 -0
- sage/numerical/backends/scip_backend.pyx +1289 -0
- sage/numerical/backends/scip_backend_test.py +13 -0
- sage/numerical/interactive_simplex_method.py +5338 -0
- sage/numerical/knapsack.py +665 -0
- sage/numerical/linear_functions.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/linear_functions.pxd +31 -0
- sage/numerical/linear_functions.pyx +1648 -0
- sage/numerical/linear_tensor.py +470 -0
- sage/numerical/linear_tensor_constraints.py +448 -0
- sage/numerical/linear_tensor_element.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/linear_tensor_element.pxd +6 -0
- sage/numerical/linear_tensor_element.pyx +459 -0
- sage/numerical/mip.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/mip.pxd +40 -0
- sage/numerical/mip.pyx +3667 -0
- sage/numerical/sdp.cpython-314-x86_64-linux-musl.so +0 -0
- sage/numerical/sdp.pxd +39 -0
- sage/numerical/sdp.pyx +1433 -0
- sage/rings/all__sagemath_polyhedra.py +3 -0
- sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
- sage/rings/polynomial/omega.py +982 -0
- sage/schemes/all__sagemath_polyhedra.py +2 -0
- sage/schemes/toric/all.py +10 -0
- sage/schemes/toric/chow_group.py +1248 -0
- sage/schemes/toric/divisor.py +2082 -0
- sage/schemes/toric/divisor_class.cpython-314-x86_64-linux-musl.so +0 -0
- sage/schemes/toric/divisor_class.pyx +322 -0
- sage/schemes/toric/fano_variety.py +1606 -0
- sage/schemes/toric/homset.py +650 -0
- sage/schemes/toric/ideal.py +451 -0
- sage/schemes/toric/library.py +1322 -0
- sage/schemes/toric/morphism.py +1958 -0
- sage/schemes/toric/points.py +1032 -0
- sage/schemes/toric/sheaf/all.py +1 -0
- sage/schemes/toric/sheaf/constructor.py +302 -0
- sage/schemes/toric/sheaf/klyachko.py +921 -0
- sage/schemes/toric/toric_subscheme.py +905 -0
- sage/schemes/toric/variety.py +3460 -0
- sage/schemes/toric/weierstrass.py +1078 -0
- sage/schemes/toric/weierstrass_covering.py +457 -0
- sage/schemes/toric/weierstrass_higher.py +288 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
- sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
- sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
- sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
- sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
sage/numerical/mip.pyx
ADDED
|
@@ -0,0 +1,3667 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-polyhedra
|
|
2
|
+
r"""
|
|
3
|
+
Mixed Integer Linear Programming
|
|
4
|
+
|
|
5
|
+
This module implements classes and methods for the efficient solving of Linear
|
|
6
|
+
Programs (:wikipedia:`LP <Linear_programming>`) and Mixed
|
|
7
|
+
Integer Linear Programs (:wikipedia:`MILP
|
|
8
|
+
<Mixed_integer_linear_programming>`).
|
|
9
|
+
|
|
10
|
+
*Do you want to understand how the simplex method works?* See the
|
|
11
|
+
:mod:`~sage.numerical.interactive_simplex_method` module (educational purposes
|
|
12
|
+
only)
|
|
13
|
+
|
|
14
|
+
Definition
|
|
15
|
+
----------
|
|
16
|
+
|
|
17
|
+
A linear program (:wikipedia:`LP <Linear_programming>`)
|
|
18
|
+
is an optimization problem (:wikipedia:`Optimization_(mathematics)`)
|
|
19
|
+
in the following form
|
|
20
|
+
|
|
21
|
+
.. MATH::
|
|
22
|
+
\max \{ c^T x \;|\; A x \leq b, x \geq 0 \}
|
|
23
|
+
|
|
24
|
+
with given `A \in \mathbb{R}^{m,n}`, `b \in \mathbb{R}^m`,
|
|
25
|
+
`c \in \mathbb{R}^n` and unknown `x \in \mathbb{R}^{n}`.
|
|
26
|
+
If some or all variables in the vector `x` are restricted over
|
|
27
|
+
the integers `\ZZ`, the problem is called mixed integer
|
|
28
|
+
linear program (:wikipedia:`MILP <Mixed_integer_linear_programming>`).
|
|
29
|
+
A wide variety of problems in optimization
|
|
30
|
+
can be formulated in this standard form. Then, solvers are
|
|
31
|
+
able to calculate a solution.
|
|
32
|
+
|
|
33
|
+
Example
|
|
34
|
+
-------
|
|
35
|
+
|
|
36
|
+
Imagine you want to solve the following linear system of three equations:
|
|
37
|
+
|
|
38
|
+
- `w_0 + w_1 + w_2 - 14 w_3 = 0`
|
|
39
|
+
- `w_1 + 2 w_2 - 8 w_3 = 0`
|
|
40
|
+
- `2 w_2 - 3 w_3 = 0`
|
|
41
|
+
|
|
42
|
+
and this additional inequality:
|
|
43
|
+
|
|
44
|
+
- `w_0 - w_1 - w_2 \geq 0`
|
|
45
|
+
|
|
46
|
+
where all `w_i \in \ZZ^+`. You know that the trivial solution is `w_i=0`,
|
|
47
|
+
but what is the first non-trivial one with `w_3 \geq 1`?
|
|
48
|
+
|
|
49
|
+
A mixed integer linear program can give you an answer:
|
|
50
|
+
|
|
51
|
+
#. You have to create an instance of :class:`MixedIntegerLinearProgram` and
|
|
52
|
+
-- in our case -- specify that it is a minimization.
|
|
53
|
+
#. Create a dictionary ``w`` of nonnegative integer variables ``w`` via ``w =
|
|
54
|
+
p.new_variable(integer=True, nonnegative=True)``.
|
|
55
|
+
#. Add those three equations as equality constraints via
|
|
56
|
+
:meth:`add_constraint <sage.numerical.mip.MixedIntegerLinearProgram.add_constraint>`.
|
|
57
|
+
#. Also add the inequality constraint.
|
|
58
|
+
#. Add an inequality constraint `w_3 \geq 1` to exclude the trivial solution.
|
|
59
|
+
#. Specify the objective function via :meth:`set_objective <sage.numerical.mip.MixedIntegerLinearProgram.set_objective>`.
|
|
60
|
+
In our case that is just `w_3`. If it
|
|
61
|
+
is a pure constraint satisfaction problem, specify it as ``None``.
|
|
62
|
+
#. To check if everything is set up correctly, you can print the problem via
|
|
63
|
+
:meth:`show <sage.numerical.mip.MixedIntegerLinearProgram.show>`.
|
|
64
|
+
#. :meth:`Solve <sage.numerical.mip.MixedIntegerLinearProgram.solve>` it and print the solution.
|
|
65
|
+
|
|
66
|
+
The following example shows all these steps::
|
|
67
|
+
|
|
68
|
+
sage: p = MixedIntegerLinearProgram(maximization=False, solver='GLPK')
|
|
69
|
+
sage: w = p.new_variable(integer=True, nonnegative=True)
|
|
70
|
+
sage: p.add_constraint(w[0] + w[1] + w[2] - 14*w[3] == 0)
|
|
71
|
+
sage: p.add_constraint(w[1] + 2*w[2] - 8*w[3] == 0)
|
|
72
|
+
sage: p.add_constraint(2*w[2] - 3*w[3] == 0)
|
|
73
|
+
sage: p.add_constraint(w[0] - w[1] - w[2] >= 0)
|
|
74
|
+
sage: p.add_constraint(w[3] >= 1)
|
|
75
|
+
sage: p.set_objective(w[3])
|
|
76
|
+
sage: p.show()
|
|
77
|
+
Minimization:
|
|
78
|
+
x_3
|
|
79
|
+
Constraints:
|
|
80
|
+
0.0 <= x_0 + x_1 + x_2 - 14.0 x_3 <= 0.0
|
|
81
|
+
0.0 <= x_1 + 2.0 x_2 - 8.0 x_3 <= 0.0
|
|
82
|
+
0.0 <= 2.0 x_2 - 3.0 x_3 <= 0.0
|
|
83
|
+
- x_0 + x_1 + x_2 <= 0.0
|
|
84
|
+
- x_3 <= -1.0
|
|
85
|
+
Variables:
|
|
86
|
+
x_0 is an integer variable (min=0.0, max=+oo)
|
|
87
|
+
x_1 is an integer variable (min=0.0, max=+oo)
|
|
88
|
+
x_2 is an integer variable (min=0.0, max=+oo)
|
|
89
|
+
x_3 is an integer variable (min=0.0, max=+oo)
|
|
90
|
+
sage: print('Objective Value: {}'.format(p.solve()))
|
|
91
|
+
Objective Value: 2.0
|
|
92
|
+
sage: for i, v in sorted(p.get_values(w, convert=ZZ, tolerance=1e-3).items()):
|
|
93
|
+
....: print(f'w_{i} = {v}')
|
|
94
|
+
w_0 = 15
|
|
95
|
+
w_1 = 10
|
|
96
|
+
w_2 = 3
|
|
97
|
+
w_3 = 2
|
|
98
|
+
|
|
99
|
+
Different backends compute with different base fields, for example::
|
|
100
|
+
|
|
101
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
102
|
+
sage: p.base_ring()
|
|
103
|
+
Real Double Field
|
|
104
|
+
sage: x = p.new_variable(real=True, nonnegative=True)
|
|
105
|
+
sage: 0.5 + 3/2*x[1]
|
|
106
|
+
0.5 + 1.5*x_0
|
|
107
|
+
|
|
108
|
+
sage: p = MixedIntegerLinearProgram(solver='ppl')
|
|
109
|
+
sage: p.base_ring()
|
|
110
|
+
Rational Field
|
|
111
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
112
|
+
sage: 0.5 + 3/2*x[1]
|
|
113
|
+
1/2 + 3/2*x_0
|
|
114
|
+
|
|
115
|
+
More about MIP variables
|
|
116
|
+
------------------------
|
|
117
|
+
|
|
118
|
+
The underlying MILP backends always work with matrices
|
|
119
|
+
where each column corresponds to a linear variable. The
|
|
120
|
+
variable corresponding to the `i`-th column (counting from 0)
|
|
121
|
+
is displayed as ``x_i``.
|
|
122
|
+
|
|
123
|
+
:class:`MixedIntegerLinearProgram` maintains a dynamic mapping
|
|
124
|
+
from the arbitrary keys indexing the components of :class:`MIPVariable`
|
|
125
|
+
objects to the backend variables (indexed by nonnegative integers).
|
|
126
|
+
Backend variables are created when a component of a :class:`MIPVariable`
|
|
127
|
+
is accessed.
|
|
128
|
+
|
|
129
|
+
To make your code more readable, you can construct one or several
|
|
130
|
+
:class:`MIPVariable` objects that can be arbitrarily named and
|
|
131
|
+
indexed. This can be done by calling :meth:`new_variable` several times,
|
|
132
|
+
or by the following special syntax::
|
|
133
|
+
|
|
134
|
+
sage: mip.<a,b> = MixedIntegerLinearProgram(solver='GLPK')
|
|
135
|
+
sage: a
|
|
136
|
+
MIPVariable a with 0 real components
|
|
137
|
+
sage: 5 + a[1] + 2*b[3]
|
|
138
|
+
5 + x_0 + 2*x_1
|
|
139
|
+
|
|
140
|
+
Indices can be any object, not necessarily integers. Multi-indices are
|
|
141
|
+
also allowed::
|
|
142
|
+
|
|
143
|
+
sage: a[4, 'string', QQ]
|
|
144
|
+
x_2
|
|
145
|
+
sage: a[4, 'string', QQ] - 7*b[2]
|
|
146
|
+
x_2 - 7*x_3
|
|
147
|
+
sage: mip.show()
|
|
148
|
+
Maximization:
|
|
149
|
+
<BLANKLINE>
|
|
150
|
+
Constraints:
|
|
151
|
+
Variables:
|
|
152
|
+
a[1] = x_0 is a continuous variable (min=-oo, max=+oo)
|
|
153
|
+
b[3] = x_1 is a continuous variable (min=-oo, max=+oo)
|
|
154
|
+
a[(4, 'string', Rational Field)] = x_2 is a continuous variable (min=-oo, max=+oo)
|
|
155
|
+
b[2] = x_3 is a continuous variable (min=-oo, max=+oo)
|
|
156
|
+
|
|
157
|
+
Upper/lower bounds on a variable can be specified either as separate constraints
|
|
158
|
+
(see :meth:`add_constraint <sage.numerical.mip.MixedIntegerLinearProgram.add_constraint>`) or
|
|
159
|
+
using the methods :meth:`set_max <sage.numerical.mip.MixedIntegerLinearProgram.set_max>`
|
|
160
|
+
and :meth:`set_min <sage.numerical.mip.MixedIntegerLinearProgram.set_min>`
|
|
161
|
+
respectively.
|
|
162
|
+
|
|
163
|
+
The default MIP variable
|
|
164
|
+
------------------------
|
|
165
|
+
|
|
166
|
+
As a special shortcut, it is not necessary to call :meth:`new_variable`.
|
|
167
|
+
A :class:`MixedIntegerLinearProgram` has a default :class:`MIPVariable`,
|
|
168
|
+
whose components are obtained by using the syntax ``mip[key]``, where
|
|
169
|
+
`key` is an arbitrary key::
|
|
170
|
+
|
|
171
|
+
sage: mip = MixedIntegerLinearProgram(solver='GLPK')
|
|
172
|
+
sage: 5 + mip[2] + 2*mip[7]
|
|
173
|
+
5 + x_0 + 2*x_1
|
|
174
|
+
|
|
175
|
+
Index of functions and methods
|
|
176
|
+
------------------------------
|
|
177
|
+
|
|
178
|
+
Below are listed the methods of :class:`MixedIntegerLinearProgram`. This module
|
|
179
|
+
also implements the :class:`MIPSolverException` exception, as well as the
|
|
180
|
+
:class:`MIPVariable` class.
|
|
181
|
+
|
|
182
|
+
.. csv-table::
|
|
183
|
+
:class: contentstable
|
|
184
|
+
:widths: 30, 70
|
|
185
|
+
:delim: |
|
|
186
|
+
|
|
187
|
+
:meth:`~MixedIntegerLinearProgram.add_constraint` | Add a constraint to the ``MixedIntegerLinearProgram``
|
|
188
|
+
:meth:`~MixedIntegerLinearProgram.base_ring` | Return the base ring
|
|
189
|
+
:meth:`~MixedIntegerLinearProgram.best_known_objective_bound`| Return the value of the currently best known bound
|
|
190
|
+
:meth:`~MixedIntegerLinearProgram.constraints` | Return a list of constraints, as 3-tuples
|
|
191
|
+
:meth:`~MixedIntegerLinearProgram.default_variable` | Return the default ``MIPVariable`` of ``self``.
|
|
192
|
+
:meth:`~MixedIntegerLinearProgram.get_backend` | Return the backend instance used
|
|
193
|
+
:meth:`~MixedIntegerLinearProgram.get_max` | Return the maximum value of a variable
|
|
194
|
+
:meth:`~MixedIntegerLinearProgram.get_min` | Return the minimum value of a variable
|
|
195
|
+
:meth:`~MixedIntegerLinearProgram.get_objective_value` | Return the value of the objective function
|
|
196
|
+
:meth:`~MixedIntegerLinearProgram.get_relative_objective_gap`| Return the relative objective gap of the best known solution
|
|
197
|
+
:meth:`~MixedIntegerLinearProgram.get_values` | Return values found by the previous call to ``solve()``
|
|
198
|
+
:meth:`~MixedIntegerLinearProgram.is_binary` | Test whether the variable ``e`` is binary
|
|
199
|
+
:meth:`~MixedIntegerLinearProgram.is_integer` | Test whether the variable is an integer
|
|
200
|
+
:meth:`~MixedIntegerLinearProgram.is_real` | Test whether the variable is real
|
|
201
|
+
:meth:`~MixedIntegerLinearProgram.linear_constraints_parent` | Return the parent for all linear constraints
|
|
202
|
+
:meth:`~MixedIntegerLinearProgram.linear_functions_parent` | Return the parent for all linear functions
|
|
203
|
+
:meth:`~MixedIntegerLinearProgram.new_variable` | Return an instance of ``MIPVariable`` associated
|
|
204
|
+
:meth:`~MixedIntegerLinearProgram.number_of_constraints` | Return the number of constraints assigned so far
|
|
205
|
+
:meth:`~MixedIntegerLinearProgram.number_of_variables` | Return the number of variables used so far
|
|
206
|
+
:meth:`~MixedIntegerLinearProgram.polyhedron` | Return the polyhedron defined by the Linear Program
|
|
207
|
+
:meth:`~MixedIntegerLinearProgram.remove_constraint` | Remove a constraint from self
|
|
208
|
+
:meth:`~MixedIntegerLinearProgram.remove_constraints` | Remove several constraints
|
|
209
|
+
:meth:`~MixedIntegerLinearProgram.set_binary` | Set a variable or a ``MIPVariable`` as binary
|
|
210
|
+
:meth:`~MixedIntegerLinearProgram.set_integer` | Set a variable or a ``MIPVariable`` as integer
|
|
211
|
+
:meth:`~MixedIntegerLinearProgram.set_max` | Set the maximum value of a variable
|
|
212
|
+
:meth:`~MixedIntegerLinearProgram.set_min` | Set the minimum value of a variable
|
|
213
|
+
:meth:`~MixedIntegerLinearProgram.set_objective` | Set the objective of the ``MixedIntegerLinearProgram``
|
|
214
|
+
:meth:`~MixedIntegerLinearProgram.set_problem_name` | Set the name of the ``MixedIntegerLinearProgram``
|
|
215
|
+
:meth:`~MixedIntegerLinearProgram.set_real` | Set a variable or a ``MIPVariable`` as real
|
|
216
|
+
:meth:`~MixedIntegerLinearProgram.show` | Display the ``MixedIntegerLinearProgram`` in a human-readable
|
|
217
|
+
:meth:`~MixedIntegerLinearProgram.solve` | Solve the ``MixedIntegerLinearProgram``
|
|
218
|
+
:meth:`~MixedIntegerLinearProgram.solver_parameter` | Return or define a solver parameter
|
|
219
|
+
:meth:`~MixedIntegerLinearProgram.sum` | Efficiently computes the sum of a sequence of LinearFunction elements
|
|
220
|
+
:meth:`~MixedIntegerLinearProgram.write_lp` | Write the linear program as a LP file
|
|
221
|
+
:meth:`~MixedIntegerLinearProgram.write_mps` | Write the linear program as a MPS file
|
|
222
|
+
|
|
223
|
+
AUTHORS:
|
|
224
|
+
|
|
225
|
+
- Risan (2012/02): added extension for exact computation
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# ****************************************************************************
|
|
229
|
+
# Copyright (C) 2012 Nathann Cohen <nathann.cohen@gmail.com>
|
|
230
|
+
#
|
|
231
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
232
|
+
# as published by the Free Software Foundation; either version 2 of
|
|
233
|
+
# the License, or (at your option) any later version.
|
|
234
|
+
# https://www.gnu.org/licenses/
|
|
235
|
+
# ****************************************************************************
|
|
236
|
+
|
|
237
|
+
from copy import copy
|
|
238
|
+
from sage.structure.element import Matrix
|
|
239
|
+
from sage.rings.integer_ring import ZZ
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
cdef class MixedIntegerLinearProgram(SageObject):
|
|
243
|
+
r"""
|
|
244
|
+
The ``MixedIntegerLinearProgram`` class is the link between Sage, linear
|
|
245
|
+
programming (LP) and mixed integer programming (MIP) solvers.
|
|
246
|
+
|
|
247
|
+
A Mixed Integer Linear Program (MILP) consists of variables, linear
|
|
248
|
+
constraints on these variables, and an objective function which is to be
|
|
249
|
+
maximised or minimised under these constraints.
|
|
250
|
+
|
|
251
|
+
See the thematic tutorial on `Linear Programming (Mixed Integer)
|
|
252
|
+
<../../../../thematic_tutorials/linear_programming.html>`_
|
|
253
|
+
or :wikipedia:`Linear_programming` for further information on linear
|
|
254
|
+
programming, and the :mod:`MILP module <sage.numerical.mip>` for its use in
|
|
255
|
+
Sage.
|
|
256
|
+
|
|
257
|
+
INPUT:
|
|
258
|
+
|
|
259
|
+
- ``solver`` -- selects a solver; see `Solvers (backends)
|
|
260
|
+
<../../../../thematic_tutorials/linear_programming.html#solvers-backends>`_
|
|
261
|
+
for more information and installation instructions for optional
|
|
262
|
+
solvers.
|
|
263
|
+
|
|
264
|
+
- ``solver="GLPK"``: the `GNU Linear Programming Kit
|
|
265
|
+
<http://www.gnu.org/software/glpk/>`_.
|
|
266
|
+
|
|
267
|
+
- ``solver="GLPK/exact"``: GLPK's implementation of an exact rational simplex
|
|
268
|
+
method.
|
|
269
|
+
|
|
270
|
+
- ``solver="Coin"``: the `COIN-OR CBC (COIN Branch and Cut) solver
|
|
271
|
+
<http://www.coin-or.org>`_.
|
|
272
|
+
|
|
273
|
+
- ``solver="CPLEX"``: provided by the proprietary `IBM ILOG CPLEX
|
|
274
|
+
Optimization Studio <https://www.ibm.com/products/ilog-cplex-optimization-studio/>`_.
|
|
275
|
+
|
|
276
|
+
- ``solver="Gurobi"``: the proprietary `Gurobi solver <http://www.gurobi.com/>`_.
|
|
277
|
+
|
|
278
|
+
- ``solver="CVXOPT"``: see the `CVXOPT <http://www.cvxopt.org/>`_ web site.
|
|
279
|
+
|
|
280
|
+
- ``solver="PPL"``: an exact rational solver (for small scale instances)
|
|
281
|
+
provided by the `Parma Polyhedra Library (PPL) <http://bugseng.com/products/ppl>`_.
|
|
282
|
+
|
|
283
|
+
- ``solver="InteractiveLP"``: a didactical
|
|
284
|
+
implementation of the revised simplex method in Sage. It works over
|
|
285
|
+
any exact ordered field, the default is ``QQ``.
|
|
286
|
+
|
|
287
|
+
- If ``solver=None`` (default), the default solver is used (see
|
|
288
|
+
:func:`default_mip_solver`).
|
|
289
|
+
|
|
290
|
+
- ``solver`` can also be a callable (such as a class),
|
|
291
|
+
see :func:`sage.numerical.backends.generic_backend.get_solver` for
|
|
292
|
+
examples.
|
|
293
|
+
|
|
294
|
+
- ``maximization``
|
|
295
|
+
|
|
296
|
+
- When set to ``True`` (default), the ``MixedIntegerLinearProgram``
|
|
297
|
+
is defined as a maximization.
|
|
298
|
+
|
|
299
|
+
- When set to ``False``, the ``MixedIntegerLinearProgram`` is
|
|
300
|
+
defined as a minimization.
|
|
301
|
+
|
|
302
|
+
- ``constraint_generation`` -- only used when ``solver=None``
|
|
303
|
+
|
|
304
|
+
- When set to ``True``, after solving the ``MixedIntegerLinearProgram``,
|
|
305
|
+
it is possible to add a constraint, and then solve it again.
|
|
306
|
+
The effect is that solvers that do not support this feature will not be
|
|
307
|
+
used.
|
|
308
|
+
|
|
309
|
+
- Defaults to ``False``.
|
|
310
|
+
|
|
311
|
+
.. SEEALSO::
|
|
312
|
+
|
|
313
|
+
- :func:`default_mip_solver` -- returns/sets the default MIP solver
|
|
314
|
+
|
|
315
|
+
EXAMPLES:
|
|
316
|
+
|
|
317
|
+
Computation of a maximum stable set in Petersen's graph::
|
|
318
|
+
|
|
319
|
+
sage: # needs sage.graphs
|
|
320
|
+
sage: g = graphs.PetersenGraph()
|
|
321
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
322
|
+
sage: b = p.new_variable(binary=True)
|
|
323
|
+
sage: p.set_objective(sum([b[v] for v in g]))
|
|
324
|
+
sage: for (u,v) in g.edges(sort=False, labels=None):
|
|
325
|
+
....: p.add_constraint(b[u] + b[v], max=1)
|
|
326
|
+
sage: p.solve(objective_only=True)
|
|
327
|
+
4.0
|
|
328
|
+
|
|
329
|
+
TESTS:
|
|
330
|
+
|
|
331
|
+
Check that :issue:`16497` is fixed::
|
|
332
|
+
|
|
333
|
+
sage: for type in ["binary", "integer"]:
|
|
334
|
+
....: k = 3
|
|
335
|
+
....: items = [1/5, 1/3, 2/3, 3/4, 5/7]
|
|
336
|
+
....: maximum = 1
|
|
337
|
+
....: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
338
|
+
....: box = p.new_variable(nonnegative=True, **{type: True})
|
|
339
|
+
....: for b in range(k):
|
|
340
|
+
....: p.add_constraint(p.sum([items[i]*box[i,b] for i in range(len(items))]) <= maximum)
|
|
341
|
+
....: for i in range(len(items)):
|
|
342
|
+
....: p.add_constraint(p.sum([box[i,b] for b in range(k)]) == 1)
|
|
343
|
+
....: p.set_objective(None)
|
|
344
|
+
....: _ = p.solve()
|
|
345
|
+
....: box = p.get_values(box)
|
|
346
|
+
....: print(all(v in ZZ for v in box.values()))
|
|
347
|
+
True
|
|
348
|
+
True
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
def __init__(self, solver=None, maximization=True,
|
|
352
|
+
constraint_generation=False, check_redundant=False,
|
|
353
|
+
names=tuple(), base_ring=None):
|
|
354
|
+
r"""
|
|
355
|
+
Constructor for the ``MixedIntegerLinearProgram`` class.
|
|
356
|
+
|
|
357
|
+
INPUT:
|
|
358
|
+
|
|
359
|
+
- ``solver`` -- one of the following:
|
|
360
|
+
|
|
361
|
+
- a string indicating one of the available solvers
|
|
362
|
+
(see :class:`MixedIntegerLinearProgram`)
|
|
363
|
+
|
|
364
|
+
- ``None`` -- (default) the default solver is used, see
|
|
365
|
+
:func:`default_mip_solver`
|
|
366
|
+
|
|
367
|
+
- or a callable (such as a class), see
|
|
368
|
+
:func:`sage.numerical.backends.generic_backend.get_solver`
|
|
369
|
+
for examples
|
|
370
|
+
|
|
371
|
+
- ``maximization``
|
|
372
|
+
|
|
373
|
+
- When set to ``True`` (default), the ``MixedIntegerLinearProgram``
|
|
374
|
+
is defined as a maximization.
|
|
375
|
+
- When set to ``False``, the ``MixedIntegerLinearProgram`` is
|
|
376
|
+
defined as a minimization.
|
|
377
|
+
|
|
378
|
+
- ``constraint_generation`` -- only used when ``solver=None``:
|
|
379
|
+
|
|
380
|
+
- When set to ``True``, after solving the
|
|
381
|
+
``MixedIntegerLinearProgram``, it is possible to add a constraint,
|
|
382
|
+
and then solve it again. The effect is that solvers that do not
|
|
383
|
+
support this feature will not be used.
|
|
384
|
+
|
|
385
|
+
- Defaults to ``False``.
|
|
386
|
+
|
|
387
|
+
- ``check_redundant`` -- whether to check that constraints added to the
|
|
388
|
+
program are redundant with constraints already in the program.
|
|
389
|
+
Only obvious redundancies are checked: to be considered redundant,
|
|
390
|
+
either a constraint is equal to another constraint in the program,
|
|
391
|
+
or it is a constant multiple of the other. To make this search
|
|
392
|
+
effective and efficient, constraints are normalized; thus, the
|
|
393
|
+
constraint `-x_1 < 0` will be stored as `x_1 > 0`.
|
|
394
|
+
|
|
395
|
+
- ``names`` -- list/tuple/iterable of string. Default names of
|
|
396
|
+
the MIP variables. Used to enable the ``MIP.<x> =
|
|
397
|
+
MixedIntegerLinearProgram()`` syntax.
|
|
398
|
+
|
|
399
|
+
.. SEEALSO::
|
|
400
|
+
|
|
401
|
+
- :meth:`default_mip_solver` -- returns/sets the default MIP solver
|
|
402
|
+
|
|
403
|
+
EXAMPLES::
|
|
404
|
+
|
|
405
|
+
sage: p = MixedIntegerLinearProgram(maximization=True)
|
|
406
|
+
|
|
407
|
+
TESTS:
|
|
408
|
+
|
|
409
|
+
Checks that the objects are deallocated without invoking the cyclic garbage
|
|
410
|
+
collector (cf. :issue:`12616`)::
|
|
411
|
+
|
|
412
|
+
sage: del p
|
|
413
|
+
sage: def just_create_variables():
|
|
414
|
+
....: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
415
|
+
....: b = p.new_variable(nonnegative=True)
|
|
416
|
+
....: p.add_constraint(b[3] + b[6] <= 2)
|
|
417
|
+
....: p.solve()
|
|
418
|
+
sage: C = sage.numerical.mip.MixedIntegerLinearProgram
|
|
419
|
+
sage: import gc
|
|
420
|
+
sage: _ = gc.collect() # avoid side effects of other doc tests
|
|
421
|
+
sage: sum([1 for x in gc.get_objects() if isinstance(x,C)])
|
|
422
|
+
0
|
|
423
|
+
|
|
424
|
+
We now disable the cyclic garbage collector. Since :issue:`12616` avoids
|
|
425
|
+
a reference cycle, the mixed integer linear program created in
|
|
426
|
+
``just_create_variables()`` is removed even without the cyclic garbage
|
|
427
|
+
collection::
|
|
428
|
+
|
|
429
|
+
sage: gc.disable()
|
|
430
|
+
sage: just_create_variables()
|
|
431
|
+
sage: sum([1 for x in gc.get_objects() if isinstance(x,C)])
|
|
432
|
+
0
|
|
433
|
+
sage: gc.enable()
|
|
434
|
+
"""
|
|
435
|
+
self.__BINARY = 0
|
|
436
|
+
self.__REAL = -1
|
|
437
|
+
self.__INTEGER = 1
|
|
438
|
+
self._first_variable_names = list(names)
|
|
439
|
+
from sage.numerical.backends.generic_backend import get_solver
|
|
440
|
+
self._backend = get_solver(solver=solver,
|
|
441
|
+
constraint_generation=constraint_generation,
|
|
442
|
+
base_ring=base_ring)
|
|
443
|
+
if not maximization:
|
|
444
|
+
self._backend.set_sense(-1)
|
|
445
|
+
|
|
446
|
+
# Associates an index to the variables
|
|
447
|
+
self._variables = {}
|
|
448
|
+
|
|
449
|
+
# Check for redundant constraints
|
|
450
|
+
self._check_redundant = check_redundant
|
|
451
|
+
if check_redundant:
|
|
452
|
+
self._constraints = list()
|
|
453
|
+
|
|
454
|
+
def linear_functions_parent(self):
|
|
455
|
+
"""
|
|
456
|
+
Return the parent for all linear functions.
|
|
457
|
+
|
|
458
|
+
EXAMPLES::
|
|
459
|
+
|
|
460
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
461
|
+
sage: p.linear_functions_parent()
|
|
462
|
+
Linear functions over Real Double Field
|
|
463
|
+
"""
|
|
464
|
+
if self._linear_functions_parent is None:
|
|
465
|
+
base_ring = self._backend.base_ring()
|
|
466
|
+
from sage.numerical.linear_functions import LinearFunctionsParent
|
|
467
|
+
self._linear_functions_parent = LinearFunctionsParent(base_ring)
|
|
468
|
+
return self._linear_functions_parent
|
|
469
|
+
|
|
470
|
+
def linear_constraints_parent(self):
|
|
471
|
+
"""
|
|
472
|
+
Return the parent for all linear constraints.
|
|
473
|
+
|
|
474
|
+
See :mod:`~sage.numerical.linear_functions` for more
|
|
475
|
+
details.
|
|
476
|
+
|
|
477
|
+
EXAMPLES::
|
|
478
|
+
|
|
479
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
480
|
+
sage: p.linear_constraints_parent()
|
|
481
|
+
Linear constraints over Real Double Field
|
|
482
|
+
"""
|
|
483
|
+
if self._linear_constraints_parent is None:
|
|
484
|
+
from sage.numerical.linear_functions import LinearConstraintsParent
|
|
485
|
+
LF = self.linear_functions_parent()
|
|
486
|
+
self._linear_constraints_parent = LinearConstraintsParent(LF)
|
|
487
|
+
return self._linear_constraints_parent
|
|
488
|
+
|
|
489
|
+
def _repr_(self):
|
|
490
|
+
r"""
|
|
491
|
+
Return a short description of the ``MixedIntegerLinearProgram``.
|
|
492
|
+
|
|
493
|
+
EXAMPLES::
|
|
494
|
+
|
|
495
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
496
|
+
sage: v = p.new_variable(binary=True)
|
|
497
|
+
sage: p.add_constraint(v[1] + v[2], max=1)
|
|
498
|
+
sage: p
|
|
499
|
+
Boolean Program (no objective, 2 variables, 1 constraint)
|
|
500
|
+
sage: p.set_objective(1); p
|
|
501
|
+
Boolean Program (constant objective, 2 variables, 1 constraint)
|
|
502
|
+
sage: p.set_objective(v[1]); p
|
|
503
|
+
Boolean Program (maximization, 2 variables, 1 constraint)
|
|
504
|
+
"""
|
|
505
|
+
cdef GenericBackend b = self._backend
|
|
506
|
+
|
|
507
|
+
cdef int nvars = b.ncols()
|
|
508
|
+
cdef int i
|
|
509
|
+
|
|
510
|
+
cdef int have_int = 0, have_bool = 0, have_cont = 0
|
|
511
|
+
for i in range(nvars):
|
|
512
|
+
if b.is_variable_binary(i):
|
|
513
|
+
have_bool = 1
|
|
514
|
+
elif b.is_variable_integer(i):
|
|
515
|
+
have_int = 1
|
|
516
|
+
else:
|
|
517
|
+
have_cont = 1
|
|
518
|
+
|
|
519
|
+
if have_cont:
|
|
520
|
+
if have_int or have_bool:
|
|
521
|
+
kind = "Mixed Integer Program"
|
|
522
|
+
else:
|
|
523
|
+
kind = "Linear Program"
|
|
524
|
+
elif have_int:
|
|
525
|
+
kind = "Integer Program"
|
|
526
|
+
elif have_bool:
|
|
527
|
+
kind = "Boolean Program"
|
|
528
|
+
else:
|
|
529
|
+
# We have no variables...
|
|
530
|
+
kind = "Mixed Integer Program"
|
|
531
|
+
|
|
532
|
+
if all(b.objective_coefficient(i) == 0 for i in range(nvars)):
|
|
533
|
+
if b.objective_constant_term():
|
|
534
|
+
minmax = "constant objective"
|
|
535
|
+
else:
|
|
536
|
+
minmax = "no objective"
|
|
537
|
+
elif b.is_maximization():
|
|
538
|
+
minmax = "maximization"
|
|
539
|
+
else:
|
|
540
|
+
minmax = "minimization"
|
|
541
|
+
|
|
542
|
+
def plural(num, noun):
|
|
543
|
+
if num != 1:
|
|
544
|
+
noun = noun + "s"
|
|
545
|
+
return f"{num} {noun}"
|
|
546
|
+
|
|
547
|
+
name = b.problem_name()
|
|
548
|
+
if name:
|
|
549
|
+
kind += f' "{name}"'
|
|
550
|
+
|
|
551
|
+
return f"{kind} ({minmax}, {plural(b.ncols(), 'variable')}, {plural(b.nrows(), 'constraint')})"
|
|
552
|
+
|
|
553
|
+
def __copy__(self):
|
|
554
|
+
r"""
|
|
555
|
+
Return a copy of the current ``MixedIntegerLinearProgram`` instance.
|
|
556
|
+
|
|
557
|
+
EXAMPLES::
|
|
558
|
+
|
|
559
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
560
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
561
|
+
sage: p.add_constraint(v[0] + v[1], max=10)
|
|
562
|
+
sage: q = copy(p)
|
|
563
|
+
sage: q.number_of_constraints()
|
|
564
|
+
1
|
|
565
|
+
|
|
566
|
+
TESTS:
|
|
567
|
+
|
|
568
|
+
Test that the default MIP variables are independent after copying::
|
|
569
|
+
|
|
570
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
571
|
+
sage: p[0]
|
|
572
|
+
x_0
|
|
573
|
+
sage: q = copy(p)
|
|
574
|
+
sage: q[0]
|
|
575
|
+
x_0
|
|
576
|
+
sage: q[1]
|
|
577
|
+
x_1
|
|
578
|
+
sage: p.number_of_variables()
|
|
579
|
+
1
|
|
580
|
+
sage: q.number_of_variables()
|
|
581
|
+
2
|
|
582
|
+
"""
|
|
583
|
+
def copying_solver(**kwdargs):
|
|
584
|
+
return (<GenericBackend> self._backend).copy()
|
|
585
|
+
|
|
586
|
+
cdef MixedIntegerLinearProgram p = \
|
|
587
|
+
MixedIntegerLinearProgram(solver=copying_solver)
|
|
588
|
+
|
|
589
|
+
p._variables = copy(self._variables)
|
|
590
|
+
|
|
591
|
+
if self._default_mipvariable is not None:
|
|
592
|
+
p._default_mipvariable = self._default_mipvariable.copy_for_mip(p)
|
|
593
|
+
|
|
594
|
+
p._check_redundant = self._check_redundant
|
|
595
|
+
p._constraints = copy(self._constraints)
|
|
596
|
+
|
|
597
|
+
return p
|
|
598
|
+
|
|
599
|
+
def __deepcopy__(self, memo={}):
|
|
600
|
+
"""
|
|
601
|
+
Return a deep copy of ``self``.
|
|
602
|
+
|
|
603
|
+
EXAMPLES::
|
|
604
|
+
|
|
605
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
606
|
+
sage: b = p.new_variable()
|
|
607
|
+
sage: p.add_constraint(b[1] + b[2] <= 6)
|
|
608
|
+
sage: p.set_objective(b[1] + b[2])
|
|
609
|
+
sage: cp = deepcopy(p)
|
|
610
|
+
sage: cp.solve()
|
|
611
|
+
6.0
|
|
612
|
+
|
|
613
|
+
TESTS:
|
|
614
|
+
|
|
615
|
+
Test that `deepcopy` makes actual copies but preserves identities::
|
|
616
|
+
|
|
617
|
+
sage: mip = MixedIntegerLinearProgram(solver='GLPK')
|
|
618
|
+
sage: ll = [mip, mip]
|
|
619
|
+
sage: dcll=deepcopy(ll)
|
|
620
|
+
sage: ll[0] is dcll[0]
|
|
621
|
+
False
|
|
622
|
+
sage: dcll[0] is dcll[1]
|
|
623
|
+
True
|
|
624
|
+
"""
|
|
625
|
+
return self.__copy__()
|
|
626
|
+
|
|
627
|
+
def __getitem__(self, v):
|
|
628
|
+
r"""
|
|
629
|
+
Return the symbolic variable corresponding to the key
|
|
630
|
+
from the default :class:`MIPVariable` instance.
|
|
631
|
+
|
|
632
|
+
It returns the element asked, creating it if necessary.
|
|
633
|
+
If necessary, it also creates the default :class:`MIPVariable` instance.
|
|
634
|
+
|
|
635
|
+
See :meth:`new_variable` for a way to have separate :class:`MIPVariable`s
|
|
636
|
+
each of which have their own key space.
|
|
637
|
+
|
|
638
|
+
EXAMPLES::
|
|
639
|
+
|
|
640
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
641
|
+
sage: p.set_objective(p['x'] + p['z'])
|
|
642
|
+
sage: p['x']
|
|
643
|
+
x_0
|
|
644
|
+
"""
|
|
645
|
+
return self.default_variable()[v]
|
|
646
|
+
|
|
647
|
+
def base_ring(self):
|
|
648
|
+
"""
|
|
649
|
+
Return the base ring.
|
|
650
|
+
|
|
651
|
+
OUTPUT: a ring. The coefficients that the chosen solver supports
|
|
652
|
+
|
|
653
|
+
EXAMPLES::
|
|
654
|
+
|
|
655
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
656
|
+
sage: p.base_ring()
|
|
657
|
+
Real Double Field
|
|
658
|
+
sage: p = MixedIntegerLinearProgram(solver='ppl')
|
|
659
|
+
sage: p.base_ring()
|
|
660
|
+
Rational Field
|
|
661
|
+
sage: from sage.rings.qqbar import AA # needs sage.rings.number_field
|
|
662
|
+
sage: p = MixedIntegerLinearProgram(solver='InteractiveLP', base_ring=AA) # needs sage.rings.number_field
|
|
663
|
+
sage: p.base_ring() # needs sage.rings.number_field
|
|
664
|
+
Algebraic Real Field
|
|
665
|
+
|
|
666
|
+
sage: # needs sage.groups sage.rings.number_field
|
|
667
|
+
sage: d = polytopes.dodecahedron()
|
|
668
|
+
sage: p = MixedIntegerLinearProgram(base_ring=d.base_ring())
|
|
669
|
+
sage: p.base_ring()
|
|
670
|
+
Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?
|
|
671
|
+
"""
|
|
672
|
+
return self._backend.base_ring()
|
|
673
|
+
|
|
674
|
+
def set_problem_name(self, name):
|
|
675
|
+
r"""
|
|
676
|
+
Set the name of the ``MixedIntegerLinearProgram``.
|
|
677
|
+
|
|
678
|
+
INPUT:
|
|
679
|
+
|
|
680
|
+
- ``name`` -- string representing the name of the
|
|
681
|
+
``MixedIntegerLinearProgram``
|
|
682
|
+
|
|
683
|
+
EXAMPLES::
|
|
684
|
+
|
|
685
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
686
|
+
sage: p.set_problem_name("Test program")
|
|
687
|
+
sage: p
|
|
688
|
+
Mixed Integer Program "Test program" (no objective, 0 variables, 0 constraints)
|
|
689
|
+
"""
|
|
690
|
+
self._backend.problem_name(name)
|
|
691
|
+
|
|
692
|
+
def new_variable(self, real=False, binary=False, integer=False, nonnegative=False, name='',
|
|
693
|
+
indices=None):
|
|
694
|
+
r"""
|
|
695
|
+
Return a new :class:`MIPVariable` instance.
|
|
696
|
+
|
|
697
|
+
A new variable ``x`` is defined by::
|
|
698
|
+
|
|
699
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
700
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
701
|
+
|
|
702
|
+
It behaves exactly as a usual dictionary would. It can use any key
|
|
703
|
+
argument you may like, as ``x[5]`` or ``x["b"]``, and has methods
|
|
704
|
+
``items()`` and ``keys()``.
|
|
705
|
+
|
|
706
|
+
.. SEEALSO::
|
|
707
|
+
|
|
708
|
+
- :meth:`set_min`, :meth:`get_min` -- set/get the lower bound of a
|
|
709
|
+
variable
|
|
710
|
+
|
|
711
|
+
- :meth:`set_max`, :meth:`get_max` -- set/get the upper bound of a
|
|
712
|
+
variable
|
|
713
|
+
|
|
714
|
+
INPUT:
|
|
715
|
+
|
|
716
|
+
- ``binary``, ``integer``, ``real`` -- boolean. Set one of these
|
|
717
|
+
arguments to ``True`` to ensure that the variable gets the
|
|
718
|
+
corresponding type.
|
|
719
|
+
|
|
720
|
+
- ``nonnegative`` -- boolean (default: ``False``); whether the
|
|
721
|
+
variable should be assumed to be nonnegative. Rather useless
|
|
722
|
+
for the binary type
|
|
723
|
+
|
|
724
|
+
- ``name`` -- string; associates a name to the variable. This
|
|
725
|
+
is only useful when exporting the linear program to a file
|
|
726
|
+
using ``write_mps`` or ``write_lp``, and has no other
|
|
727
|
+
effect.
|
|
728
|
+
|
|
729
|
+
- ``indices`` -- (optional) an iterable of keys; components
|
|
730
|
+
corresponding to these keys are created in order,
|
|
731
|
+
and access to components with other keys will raise an
|
|
732
|
+
error; otherwise components of this variable can be
|
|
733
|
+
indexed by arbitrary keys and are created dynamically
|
|
734
|
+
on access
|
|
735
|
+
|
|
736
|
+
OUTPUT:
|
|
737
|
+
|
|
738
|
+
A new instance of :class:`MIPVariable` associated to the
|
|
739
|
+
current :class:`MixedIntegerLinearProgram`.
|
|
740
|
+
|
|
741
|
+
EXAMPLES::
|
|
742
|
+
|
|
743
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
744
|
+
sage: x = p.new_variable(); x
|
|
745
|
+
MIPVariable with 0 real components
|
|
746
|
+
sage: x0 = x[0]; x0
|
|
747
|
+
x_0
|
|
748
|
+
|
|
749
|
+
By default, variables are unbounded::
|
|
750
|
+
|
|
751
|
+
sage: print(p.get_min(x0))
|
|
752
|
+
None
|
|
753
|
+
sage: print(p.get_max(x0))
|
|
754
|
+
None
|
|
755
|
+
|
|
756
|
+
To define two dictionaries of variables, the first being
|
|
757
|
+
of real type, and the second of integer type ::
|
|
758
|
+
|
|
759
|
+
sage: x = p.new_variable(real=True, nonnegative=True)
|
|
760
|
+
sage: y = p.new_variable(integer=True, nonnegative=True)
|
|
761
|
+
sage: p.add_constraint(x[2] + y[3,5], max=2)
|
|
762
|
+
sage: p.is_integer(x[2])
|
|
763
|
+
False
|
|
764
|
+
sage: p.is_integer(y[3,5])
|
|
765
|
+
True
|
|
766
|
+
|
|
767
|
+
An exception is raised when two types are supplied ::
|
|
768
|
+
|
|
769
|
+
sage: z = p.new_variable(real=True, integer=True)
|
|
770
|
+
Traceback (most recent call last):
|
|
771
|
+
...
|
|
772
|
+
ValueError: Exactly one of the available types has to be True
|
|
773
|
+
|
|
774
|
+
Unbounded variables::
|
|
775
|
+
|
|
776
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
777
|
+
sage: x = p.new_variable(real=True)
|
|
778
|
+
sage: y = p.new_variable(integer=True)
|
|
779
|
+
sage: p.add_constraint(x[0] + x[3] <= 8)
|
|
780
|
+
sage: p.add_constraint(y[0] >= y[1])
|
|
781
|
+
sage: p.show()
|
|
782
|
+
Maximization:
|
|
783
|
+
<BLANKLINE>
|
|
784
|
+
Constraints:
|
|
785
|
+
x_0 + x_1 <= 8.0
|
|
786
|
+
- x_2 + x_3 <= 0.0
|
|
787
|
+
Variables:
|
|
788
|
+
x_0 is a continuous variable (min=-oo, max=+oo)
|
|
789
|
+
x_1 is a continuous variable (min=-oo, max=+oo)
|
|
790
|
+
x_2 is an integer variable (min=-oo, max=+oo)
|
|
791
|
+
x_3 is an integer variable (min=-oo, max=+oo)
|
|
792
|
+
|
|
793
|
+
On the Sage command line, generator syntax is accepted as a
|
|
794
|
+
shorthand for generating new variables with default settings::
|
|
795
|
+
|
|
796
|
+
sage: mip.<x, y, z> = MixedIntegerLinearProgram(solver='GLPK')
|
|
797
|
+
sage: mip.add_constraint(x[0] + y[1] + z[2] <= 10)
|
|
798
|
+
sage: mip.show()
|
|
799
|
+
Maximization:
|
|
800
|
+
<BLANKLINE>
|
|
801
|
+
Constraints:
|
|
802
|
+
x[0] + y[1] + z[2] <= 10.0
|
|
803
|
+
Variables:
|
|
804
|
+
x[0] = x_0 is a continuous variable (min=-oo, max=+oo)
|
|
805
|
+
y[1] = x_1 is a continuous variable (min=-oo, max=+oo)
|
|
806
|
+
z[2] = x_2 is a continuous variable (min=-oo, max=+oo)
|
|
807
|
+
"""
|
|
808
|
+
if sum([real, binary, integer]) > 1:
|
|
809
|
+
raise ValueError("Exactly one of the available types has to be True")
|
|
810
|
+
|
|
811
|
+
if binary:
|
|
812
|
+
vtype = self.__BINARY
|
|
813
|
+
elif integer:
|
|
814
|
+
vtype = self.__INTEGER
|
|
815
|
+
else:
|
|
816
|
+
vtype = self.__REAL
|
|
817
|
+
|
|
818
|
+
if not name and self._first_variable_names:
|
|
819
|
+
name = self._first_variable_names.pop(0)
|
|
820
|
+
|
|
821
|
+
return MIPVariable(self,
|
|
822
|
+
vtype,
|
|
823
|
+
name=name,
|
|
824
|
+
lower_bound=0 if (nonnegative or binary) else None,
|
|
825
|
+
upper_bound=1 if binary else None,
|
|
826
|
+
indices=indices)
|
|
827
|
+
|
|
828
|
+
def default_variable(self):
|
|
829
|
+
"""
|
|
830
|
+
Return the default :class:`MIPVariable` of ``self``.
|
|
831
|
+
|
|
832
|
+
EXAMPLES::
|
|
833
|
+
|
|
834
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
835
|
+
sage: p.default_variable()
|
|
836
|
+
MIPVariable with 0 real components
|
|
837
|
+
"""
|
|
838
|
+
if self._default_mipvariable is None:
|
|
839
|
+
self._default_mipvariable = self.new_variable()
|
|
840
|
+
return self._default_mipvariable
|
|
841
|
+
|
|
842
|
+
def _first_ngens(self, n):
|
|
843
|
+
"""
|
|
844
|
+
Construct the first `n` :class:`MIPVariable`s.
|
|
845
|
+
|
|
846
|
+
This method is used for the generator syntax (see below). You
|
|
847
|
+
probably shouldn't use it for anything else.
|
|
848
|
+
|
|
849
|
+
INPUT:
|
|
850
|
+
|
|
851
|
+
- ``n`` -- integer; the number of variables to construct
|
|
852
|
+
|
|
853
|
+
OUTPUT:
|
|
854
|
+
|
|
855
|
+
A tuple of :class:`MIPVariable` instances.
|
|
856
|
+
They are created as free continuous variables.
|
|
857
|
+
|
|
858
|
+
EXAMPLES::
|
|
859
|
+
|
|
860
|
+
sage: mip.<a,b> = MixedIntegerLinearProgram(solver='GLPK') # indirect doctest
|
|
861
|
+
sage: a[0] + b[2]
|
|
862
|
+
x_0 + x_1
|
|
863
|
+
sage: mip.show()
|
|
864
|
+
Maximization:
|
|
865
|
+
<BLANKLINE>
|
|
866
|
+
Constraints:
|
|
867
|
+
Variables:
|
|
868
|
+
a[0] = x_0 is a continuous variable (min=-oo, max=+oo)
|
|
869
|
+
b[2] = x_1 is a continuous variable (min=-oo, max=+oo)
|
|
870
|
+
"""
|
|
871
|
+
return tuple(self.new_variable() for i in range(n))
|
|
872
|
+
|
|
873
|
+
cpdef int number_of_constraints(self) noexcept:
|
|
874
|
+
r"""
|
|
875
|
+
Return the number of constraints assigned so far.
|
|
876
|
+
|
|
877
|
+
EXAMPLES::
|
|
878
|
+
|
|
879
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
880
|
+
sage: p.add_constraint(p[0] - p[2], min=1, max=4)
|
|
881
|
+
sage: p.add_constraint(p[0] - 2*p[1], min=1)
|
|
882
|
+
sage: p.number_of_constraints()
|
|
883
|
+
2
|
|
884
|
+
"""
|
|
885
|
+
return self._backend.nrows()
|
|
886
|
+
|
|
887
|
+
cpdef int number_of_variables(self) noexcept:
|
|
888
|
+
r"""
|
|
889
|
+
Return the number of variables used so far.
|
|
890
|
+
|
|
891
|
+
Note that this is backend-dependent, i.e. we count solver's
|
|
892
|
+
variables rather than user's variables. An example of the latter
|
|
893
|
+
can be seen below: Gurobi converts double inequalities,
|
|
894
|
+
i.e. inequalities like `m <= c^T x <= M`, with `m<M`, into
|
|
895
|
+
equations, by adding extra variables: `c^T x + y = M`, `0 <= y
|
|
896
|
+
<= M-m`.
|
|
897
|
+
|
|
898
|
+
EXAMPLES::
|
|
899
|
+
|
|
900
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
901
|
+
sage: p.add_constraint(p[0] - p[2], max=4)
|
|
902
|
+
sage: p.number_of_variables()
|
|
903
|
+
2
|
|
904
|
+
sage: p.add_constraint(p[0] - 2*p[1], min=1)
|
|
905
|
+
sage: p.number_of_variables()
|
|
906
|
+
3
|
|
907
|
+
sage: p = MixedIntegerLinearProgram(solver='glpk')
|
|
908
|
+
sage: p.add_constraint(p[0] - p[2], min=1, max=4)
|
|
909
|
+
sage: p.number_of_variables()
|
|
910
|
+
2
|
|
911
|
+
sage: p = MixedIntegerLinearProgram(solver='gurobi') # optional - Gurobi
|
|
912
|
+
sage: p.add_constraint(p[0] - p[2], min=1, max=4) # optional - Gurobi
|
|
913
|
+
sage: p.number_of_variables() # optional - Gurobi
|
|
914
|
+
3
|
|
915
|
+
"""
|
|
916
|
+
return self._backend.ncols()
|
|
917
|
+
|
|
918
|
+
def constraints(self, indices=None):
|
|
919
|
+
r"""
|
|
920
|
+
Return a list of constraints, as 3-tuples.
|
|
921
|
+
|
|
922
|
+
INPUT:
|
|
923
|
+
|
|
924
|
+
- ``indices`` -- select which constraint(s) to return
|
|
925
|
+
|
|
926
|
+
- If ``indices = None``, the method returns the list of all the
|
|
927
|
+
constraints.
|
|
928
|
+
|
|
929
|
+
- If ``indices`` is an integer `i`, the method returns constraint
|
|
930
|
+
`i`.
|
|
931
|
+
|
|
932
|
+
- If ``indices`` is a list of integers, the method returns the list
|
|
933
|
+
of the corresponding constraints.
|
|
934
|
+
|
|
935
|
+
OUTPUT:
|
|
936
|
+
|
|
937
|
+
Each constraint is returned as a triple ``lower_bound, (indices,
|
|
938
|
+
coefficients), upper_bound``. For each of those entries, the
|
|
939
|
+
corresponding linear function is the one associating to variable
|
|
940
|
+
``indices[i]`` the coefficient ``coefficients[i]``, and `0` to all the
|
|
941
|
+
others.
|
|
942
|
+
|
|
943
|
+
``lower_bound`` and ``upper_bound`` are numerical values.
|
|
944
|
+
|
|
945
|
+
EXAMPLES:
|
|
946
|
+
|
|
947
|
+
First, let us define a small LP::
|
|
948
|
+
|
|
949
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
950
|
+
sage: p.add_constraint(p[0] - p[2], min=1, max=4)
|
|
951
|
+
sage: p.add_constraint(p[0] - 2*p[1], min=1)
|
|
952
|
+
|
|
953
|
+
To obtain the list of all constraints::
|
|
954
|
+
|
|
955
|
+
sage: p.constraints() # not tested
|
|
956
|
+
[(1.0, ([1, 0], [-1.0, 1.0]), 4.0), (1.0, ([2, 0], [-2.0, 1.0]), None)]
|
|
957
|
+
|
|
958
|
+
Or constraint `0` only::
|
|
959
|
+
|
|
960
|
+
sage: p.constraints(0) # not tested
|
|
961
|
+
(1.0, ([1, 0], [-1.0, 1.0]), 4.0)
|
|
962
|
+
|
|
963
|
+
A list of constraints containing only `1`::
|
|
964
|
+
|
|
965
|
+
sage: p.constraints([1]) # not tested
|
|
966
|
+
[(1.0, ([2, 0], [-2.0, 1.0]), None)]
|
|
967
|
+
|
|
968
|
+
TESTS:
|
|
969
|
+
|
|
970
|
+
As the ordering of the variables in each constraint depends on the
|
|
971
|
+
solver used, we define a short function reordering it before it is
|
|
972
|
+
printed. The output would look the same without this function applied::
|
|
973
|
+
|
|
974
|
+
sage: def reorder_constraint(lb, indcoef, ub):
|
|
975
|
+
....: ind, coef = indcoef
|
|
976
|
+
....: d = dict(zip(ind, coef))
|
|
977
|
+
....: ind.sort()
|
|
978
|
+
....: return (lb, (ind, [d[i] for i in ind]), ub)
|
|
979
|
+
|
|
980
|
+
Running the examples from above, reordering applied::
|
|
981
|
+
|
|
982
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
983
|
+
sage: p.add_constraint(p[0] - p[2], min=1, max=4)
|
|
984
|
+
sage: p.add_constraint(p[0] - 2*p[1], min=1)
|
|
985
|
+
sage: sorted(reorder_constraint(*c) for c in p.constraints())
|
|
986
|
+
[(1.0, ([0, 1], [1.0, -1.0]), 4.0), (1.0, ([0, 2], [1.0, -2.0]), None)]
|
|
987
|
+
sage: reorder_constraint(*p.constraints(0))
|
|
988
|
+
(1.0, ([0, 1], [1.0, -1.0]), 4.0)
|
|
989
|
+
sage: sorted(reorder_constraint(*c) for c in p.constraints([1]))
|
|
990
|
+
[(1.0, ([0, 2], [1.0, -2.0]), None)]
|
|
991
|
+
"""
|
|
992
|
+
from sage.rings.integer import Integer as Integer
|
|
993
|
+
cdef int i
|
|
994
|
+
cdef GenericBackend b = self._backend
|
|
995
|
+
|
|
996
|
+
result = []
|
|
997
|
+
|
|
998
|
+
# If indices is None, we actually want to return all constraints
|
|
999
|
+
if indices is None:
|
|
1000
|
+
indices = list(range(b.nrows()))
|
|
1001
|
+
|
|
1002
|
+
# Only one constraint
|
|
1003
|
+
if isinstance(indices, (int, Integer)):
|
|
1004
|
+
lb, ub = b.row_bounds(indices)
|
|
1005
|
+
return (lb, b.row(indices), ub)
|
|
1006
|
+
|
|
1007
|
+
# List of constraints
|
|
1008
|
+
if isinstance(indices, list):
|
|
1009
|
+
for i in indices:
|
|
1010
|
+
lb, ub = b.row_bounds(i)
|
|
1011
|
+
result.append((lb, b.row(i), ub))
|
|
1012
|
+
|
|
1013
|
+
return result
|
|
1014
|
+
|
|
1015
|
+
# Weird Input
|
|
1016
|
+
raise ValueError("constraints() requires a list of integers, though it will accommodate None or an integer.")
|
|
1017
|
+
|
|
1018
|
+
def polyhedron(self, **kwds):
|
|
1019
|
+
r"""
|
|
1020
|
+
Return the polyhedron defined by the Linear Program.
|
|
1021
|
+
|
|
1022
|
+
INPUT:
|
|
1023
|
+
|
|
1024
|
+
All arguments given to this method are forwarded to the constructor of
|
|
1025
|
+
the :func:`Polyhedron` class.
|
|
1026
|
+
|
|
1027
|
+
OUTPUT:
|
|
1028
|
+
|
|
1029
|
+
A :func:`Polyhedron` object whose `i`-th variable represents the `i`-th
|
|
1030
|
+
variable of ``self``.
|
|
1031
|
+
|
|
1032
|
+
.. warning::
|
|
1033
|
+
|
|
1034
|
+
The polyhedron is built from the variables stored by the LP solver
|
|
1035
|
+
(i.e. the output of :meth:`show`). While they usually match the ones
|
|
1036
|
+
created explicitly when defining the LP, a solver like Gurobi has
|
|
1037
|
+
been known to introduce additional variables to store constraints of
|
|
1038
|
+
the type ``lower_bound <= linear_function <= upper bound``. You
|
|
1039
|
+
should be fine if you did not install Gurobi or if you do not use it
|
|
1040
|
+
as a solver, but keep an eye on the number of variables in the
|
|
1041
|
+
polyhedron, or on the output of :meth:`show`. Just in case.
|
|
1042
|
+
|
|
1043
|
+
.. SEEALSO::
|
|
1044
|
+
|
|
1045
|
+
:meth:`~sage.geometry.polyhedron.base.Polyhedron_base.to_linear_program`
|
|
1046
|
+
-- return the :class:`MixedIntegerLinearProgram` object associated
|
|
1047
|
+
with a :func:`Polyhedron` object.
|
|
1048
|
+
|
|
1049
|
+
EXAMPLES:
|
|
1050
|
+
|
|
1051
|
+
A LP on two variables::
|
|
1052
|
+
|
|
1053
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1054
|
+
sage: p.add_constraint(0 <= 2*p['x'] + p['y'] <= 1)
|
|
1055
|
+
sage: p.add_constraint(0 <= 3*p['y'] + p['x'] <= 2)
|
|
1056
|
+
sage: P = p.polyhedron(); P # needs cddexec
|
|
1057
|
+
A 2-dimensional polyhedron in RDF^2 defined as the convex hull of 4 vertices
|
|
1058
|
+
|
|
1059
|
+
3-D Polyhedron::
|
|
1060
|
+
|
|
1061
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1062
|
+
sage: p.add_constraint(0 <= 2*p['x'] + p['y'] + 3*p['z'] <= 1)
|
|
1063
|
+
sage: p.add_constraint(0 <= 2*p['y'] + p['z'] + 3*p['x'] <= 1)
|
|
1064
|
+
sage: p.add_constraint(0 <= 2*p['z'] + p['x'] + 3*p['y'] <= 1)
|
|
1065
|
+
sage: P = p.polyhedron(); P # needs cddexec
|
|
1066
|
+
A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 8 vertices
|
|
1067
|
+
|
|
1068
|
+
An empty polyhedron::
|
|
1069
|
+
|
|
1070
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1071
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
1072
|
+
sage: p.add_constraint(2*v['x'] + v['y'] + 3*v['z'] <= 1)
|
|
1073
|
+
sage: p.add_constraint(2*v['y'] + v['z'] + 3*v['x'] <= 1)
|
|
1074
|
+
sage: p.add_constraint(2*v['z'] + v['x'] + 3*v['y'] >= 2)
|
|
1075
|
+
sage: P = p.polyhedron(); P # needs cddexec
|
|
1076
|
+
The empty polyhedron in RDF^3
|
|
1077
|
+
|
|
1078
|
+
An unbounded polyhedron::
|
|
1079
|
+
|
|
1080
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1081
|
+
sage: p.add_constraint(2*p['x'] + p['y'] - p['z'] <= 1)
|
|
1082
|
+
sage: P = p.polyhedron(); P # needs cddexec
|
|
1083
|
+
A 3-dimensional polyhedron in RDF^3 defined as the convex hull of 1 vertex, 1 ray, 2 lines
|
|
1084
|
+
|
|
1085
|
+
A square (see :issue:`14395`) ::
|
|
1086
|
+
|
|
1087
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1088
|
+
sage: x,y = p['x'], p['y']
|
|
1089
|
+
sage: p.add_constraint(x <= 1)
|
|
1090
|
+
sage: p.add_constraint(x >= -1)
|
|
1091
|
+
sage: p.add_constraint(y <= 1)
|
|
1092
|
+
sage: p.add_constraint(y >= -1)
|
|
1093
|
+
sage: p.polyhedron() # needs cddexec
|
|
1094
|
+
A 2-dimensional polyhedron in RDF^2 defined as the convex hull of 4 vertices
|
|
1095
|
+
|
|
1096
|
+
We can also use a backend that supports exact arithmetic::
|
|
1097
|
+
|
|
1098
|
+
sage: p = MixedIntegerLinearProgram(solver='PPL')
|
|
1099
|
+
sage: x,y = p['x'], p['y']
|
|
1100
|
+
sage: p.add_constraint(x <= 1)
|
|
1101
|
+
sage: p.add_constraint(x >= -1)
|
|
1102
|
+
sage: p.add_constraint(y <= 1)
|
|
1103
|
+
sage: p.add_constraint(y >= -1)
|
|
1104
|
+
sage: p.polyhedron() # needs cddexec
|
|
1105
|
+
A 2-dimensional polyhedron in QQ^2 defined as the convex hull of 4 vertices
|
|
1106
|
+
|
|
1107
|
+
TESTS:
|
|
1108
|
+
|
|
1109
|
+
Check if :issue:`23326` is fixed::
|
|
1110
|
+
|
|
1111
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1112
|
+
sage: x, y = p['x'], p['y']
|
|
1113
|
+
sage: p.set_min(x, 0); p.set_min(y, 0)
|
|
1114
|
+
sage: p.set_objective(3.5*x + 2.5*y)
|
|
1115
|
+
sage: p.add_constraint(x + y <= 10)
|
|
1116
|
+
sage: p.add_constraint(18.5*x + 5.1*y <= 110.3)
|
|
1117
|
+
sage: p.polyhedron() # needs cddexec
|
|
1118
|
+
A 2-dimensional polyhedron in RDF^2 defined as the convex hull of 4 vertices
|
|
1119
|
+
"""
|
|
1120
|
+
from sage.geometry.polyhedron.constructor import Polyhedron
|
|
1121
|
+
cdef GenericBackend b = self._backend
|
|
1122
|
+
cdef int i
|
|
1123
|
+
|
|
1124
|
+
# Constraints
|
|
1125
|
+
inequalities = []
|
|
1126
|
+
equalities = []
|
|
1127
|
+
nvar = self.number_of_variables()
|
|
1128
|
+
for lb, (indices, values), ub in self.constraints():
|
|
1129
|
+
coeffs = dict(zip(indices, values))
|
|
1130
|
+
# Equalities
|
|
1131
|
+
if (lb is not None) and lb == ub:
|
|
1132
|
+
linear_function = []
|
|
1133
|
+
linear_function = [coeffs.get(i,0) for i in range(nvar)]
|
|
1134
|
+
linear_function.insert(0,-lb)
|
|
1135
|
+
equalities.append(linear_function)
|
|
1136
|
+
continue
|
|
1137
|
+
# Lower Bound
|
|
1138
|
+
if lb is not None:
|
|
1139
|
+
linear_function = []
|
|
1140
|
+
linear_function = [coeffs.get(i,0) for i in range(nvar)]
|
|
1141
|
+
linear_function.insert(0,-lb)
|
|
1142
|
+
inequalities.append(linear_function)
|
|
1143
|
+
# Upper Bound
|
|
1144
|
+
if ub is not None:
|
|
1145
|
+
linear_function = []
|
|
1146
|
+
linear_function = [-coeffs.get(i,0) for i in range(nvar)]
|
|
1147
|
+
linear_function.insert(0,ub)
|
|
1148
|
+
inequalities.append(linear_function)
|
|
1149
|
+
|
|
1150
|
+
# Variable bounds
|
|
1151
|
+
zero = [0] * nvar
|
|
1152
|
+
for 0<= i < nvar:
|
|
1153
|
+
lb, ub = b.col_bounds(i)
|
|
1154
|
+
# Fixed variable
|
|
1155
|
+
if (lb is not None) and lb == ub:
|
|
1156
|
+
linear_function = copy(zero)
|
|
1157
|
+
linear_function[i] = 1
|
|
1158
|
+
linear_function.insert(0,-lb)
|
|
1159
|
+
equalities.append(linear_function)
|
|
1160
|
+
continue
|
|
1161
|
+
# Lower bound
|
|
1162
|
+
if lb is not None:
|
|
1163
|
+
linear_function = copy(zero)
|
|
1164
|
+
linear_function[i] = 1
|
|
1165
|
+
linear_function.insert(0,-lb)
|
|
1166
|
+
inequalities.append(linear_function)
|
|
1167
|
+
# Upper bound
|
|
1168
|
+
if ub is not None:
|
|
1169
|
+
linear_function = copy(zero)
|
|
1170
|
+
linear_function[i] = -1
|
|
1171
|
+
linear_function.insert(0,ub)
|
|
1172
|
+
inequalities.append(linear_function)
|
|
1173
|
+
return Polyhedron(ieqs=inequalities, eqns=equalities, **kwds)
|
|
1174
|
+
|
|
1175
|
+
def show(self):
|
|
1176
|
+
r"""
|
|
1177
|
+
Displays the ``MixedIntegerLinearProgram`` in a human-readable
|
|
1178
|
+
way.
|
|
1179
|
+
|
|
1180
|
+
EXAMPLES:
|
|
1181
|
+
|
|
1182
|
+
When constraints and variables have names ::
|
|
1183
|
+
|
|
1184
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1185
|
+
sage: x = p.new_variable(name='Hey')
|
|
1186
|
+
sage: p.set_objective(x[1] + x[2])
|
|
1187
|
+
sage: p.add_constraint(-3*x[1] + 2*x[2], max=2, name='Constraint_1')
|
|
1188
|
+
sage: p.show()
|
|
1189
|
+
Maximization:
|
|
1190
|
+
Hey[1] + Hey[2]
|
|
1191
|
+
Constraints:
|
|
1192
|
+
Constraint_1: -3.0 Hey[1] + 2.0 Hey[2] <= 2.0
|
|
1193
|
+
Variables:
|
|
1194
|
+
Hey[1] = x_0 is a continuous variable (min=-oo, max=+oo)
|
|
1195
|
+
Hey[2] = x_1 is a continuous variable (min=-oo, max=+oo)
|
|
1196
|
+
|
|
1197
|
+
Without any names ::
|
|
1198
|
+
|
|
1199
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1200
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1201
|
+
sage: p.set_objective(x[1] + x[2])
|
|
1202
|
+
sage: p.add_constraint(-3*x[1] + 2*x[2], max=2)
|
|
1203
|
+
sage: p.show()
|
|
1204
|
+
Maximization:
|
|
1205
|
+
x_0 + x_1
|
|
1206
|
+
Constraints:
|
|
1207
|
+
-3.0 x_0 + 2.0 x_1 <= 2.0
|
|
1208
|
+
Variables:
|
|
1209
|
+
x_0 is a continuous variable (min=0.0, max=+oo)
|
|
1210
|
+
x_1 is a continuous variable (min=0.0, max=+oo)
|
|
1211
|
+
|
|
1212
|
+
With `\QQ` coefficients::
|
|
1213
|
+
|
|
1214
|
+
sage: p = MixedIntegerLinearProgram(solver='ppl')
|
|
1215
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1216
|
+
sage: p.set_objective(x[1] + 1/2*x[2])
|
|
1217
|
+
sage: p.add_constraint(-3/5*x[1] + 2/7*x[2], max=2/5)
|
|
1218
|
+
sage: p.show()
|
|
1219
|
+
Maximization:
|
|
1220
|
+
x_0 + 1/2 x_1
|
|
1221
|
+
Constraints:
|
|
1222
|
+
constraint_0: -3/5 x_0 + 2/7 x_1 <= 2/5
|
|
1223
|
+
Variables:
|
|
1224
|
+
x_0 is a continuous variable (min=0, max=+oo)
|
|
1225
|
+
x_1 is a continuous variable (min=0, max=+oo)
|
|
1226
|
+
|
|
1227
|
+
With a constant term in the objective::
|
|
1228
|
+
|
|
1229
|
+
sage: p = MixedIntegerLinearProgram(solver='ppl')
|
|
1230
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1231
|
+
sage: p.set_objective(x[0] + 42)
|
|
1232
|
+
sage: p.show()
|
|
1233
|
+
Maximization:
|
|
1234
|
+
x_0 + 42
|
|
1235
|
+
Constraints:
|
|
1236
|
+
Variables:
|
|
1237
|
+
x_0 is a continuous variable (min=0, max=+oo)
|
|
1238
|
+
"""
|
|
1239
|
+
cdef int i, j
|
|
1240
|
+
cdef GenericBackend b = self._backend
|
|
1241
|
+
|
|
1242
|
+
# varid_name associates variables id to names
|
|
1243
|
+
varid_name = {}
|
|
1244
|
+
varid_explainer = {}
|
|
1245
|
+
for 0<= i < b.ncols():
|
|
1246
|
+
s = b.col_name(i)
|
|
1247
|
+
default_name = str(self.linear_functions_parent()({i: 1}))
|
|
1248
|
+
if s and s != default_name:
|
|
1249
|
+
varid_name[i] = s
|
|
1250
|
+
varid_explainer[i] = '{0} = {1}'.format(s, default_name)
|
|
1251
|
+
else:
|
|
1252
|
+
varid_explainer[i] = varid_name[i] = default_name
|
|
1253
|
+
|
|
1254
|
+
##### Sense and objective function
|
|
1255
|
+
print("Maximization:" if b.is_maximization() else "Minimization:")
|
|
1256
|
+
print(" ", end=" ")
|
|
1257
|
+
first = True
|
|
1258
|
+
for 0<= i< b.ncols():
|
|
1259
|
+
c = b.objective_coefficient(i)
|
|
1260
|
+
if c == 0:
|
|
1261
|
+
continue
|
|
1262
|
+
print((("+ " if (not first and c>0) else "") +
|
|
1263
|
+
("" if c == 1 else ("- " if c == -1 else str(c)+" "))+varid_name[i]
|
|
1264
|
+
), end=" ")
|
|
1265
|
+
first = False
|
|
1266
|
+
d = b.objective_constant_term()
|
|
1267
|
+
if d > self._backend.zero():
|
|
1268
|
+
print("+ {} ".format(d))
|
|
1269
|
+
elif d < self._backend.zero():
|
|
1270
|
+
print("- {} ".format(-d))
|
|
1271
|
+
print("\n")
|
|
1272
|
+
|
|
1273
|
+
##### Constraints
|
|
1274
|
+
print("Constraints:")
|
|
1275
|
+
for 0<= i < b.nrows():
|
|
1276
|
+
indices, values = b.row(i)
|
|
1277
|
+
lb, ub = b.row_bounds(i)
|
|
1278
|
+
print(" ", end=" ")
|
|
1279
|
+
# Constraint's name
|
|
1280
|
+
if b.row_name(i):
|
|
1281
|
+
print(b.row_name(i)+":", end=" ")
|
|
1282
|
+
# Lower bound
|
|
1283
|
+
if lb is not None:
|
|
1284
|
+
print(str(lb)+" <=", end=" ")
|
|
1285
|
+
first = True
|
|
1286
|
+
for j, c in sorted(zip(indices, values)):
|
|
1287
|
+
if c == 0:
|
|
1288
|
+
continue
|
|
1289
|
+
print((("+ " if (not first and c>0) else "") +
|
|
1290
|
+
("" if c == 1 else ("- " if c == -1 else (str(c) + " " if first and c < 0 else ("- " + str(abs(c)) + " " if c < 0 else str(c) + " "))))+varid_name[j]
|
|
1291
|
+
), end=" ")
|
|
1292
|
+
first = False
|
|
1293
|
+
# Upper bound
|
|
1294
|
+
print("<= "+str(ub) if ub is not None else "")
|
|
1295
|
+
|
|
1296
|
+
##### Variables
|
|
1297
|
+
print("Variables:")
|
|
1298
|
+
for 0<= i < b.ncols():
|
|
1299
|
+
if b.is_variable_integer(i):
|
|
1300
|
+
var_type = 'an integer'
|
|
1301
|
+
elif b.is_variable_binary(i):
|
|
1302
|
+
var_type = 'a boolean'
|
|
1303
|
+
else:
|
|
1304
|
+
var_type = 'a continuous'
|
|
1305
|
+
name = varid_explainer[i]
|
|
1306
|
+
lb, ub = b.col_bounds(i)
|
|
1307
|
+
print(' {0} is {1} variable (min={2}, max={3})'.format(
|
|
1308
|
+
name, var_type,
|
|
1309
|
+
lb if lb is not None else "-oo",
|
|
1310
|
+
ub if ub is not None else "+oo"))
|
|
1311
|
+
|
|
1312
|
+
def write_mps(self, filename, modern=True):
|
|
1313
|
+
r"""
|
|
1314
|
+
Write the linear program as a MPS file.
|
|
1315
|
+
|
|
1316
|
+
This function export the problem as a MPS file.
|
|
1317
|
+
|
|
1318
|
+
INPUT:
|
|
1319
|
+
|
|
1320
|
+
- ``filename`` -- the file in which you want the problem
|
|
1321
|
+
to be written
|
|
1322
|
+
|
|
1323
|
+
- ``modern`` -- lets you choose between Fixed MPS and Free MPS:
|
|
1324
|
+
|
|
1325
|
+
- ``True`` -- outputs the problem in Free MPS
|
|
1326
|
+
- ``False`` -- outputs the problem in Fixed MPS
|
|
1327
|
+
|
|
1328
|
+
EXAMPLES::
|
|
1329
|
+
|
|
1330
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1331
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1332
|
+
sage: p.set_objective(x[1] + x[2])
|
|
1333
|
+
sage: p.add_constraint(-3*x[1] + 2*x[2], max=2, name='OneConstraint')
|
|
1334
|
+
sage: import tempfile
|
|
1335
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.mps') as f:
|
|
1336
|
+
....: p.write_mps(f.name)
|
|
1337
|
+
Writing problem data to ...
|
|
1338
|
+
17 records were written
|
|
1339
|
+
|
|
1340
|
+
For information about the MPS file format, see
|
|
1341
|
+
:wikipedia:`MPS_(format)`
|
|
1342
|
+
"""
|
|
1343
|
+
self._backend.write_mps(filename, modern)
|
|
1344
|
+
|
|
1345
|
+
def write_lp(self, filename):
|
|
1346
|
+
r"""
|
|
1347
|
+
Write the linear program as a LP file.
|
|
1348
|
+
|
|
1349
|
+
This function export the problem as a LP file.
|
|
1350
|
+
|
|
1351
|
+
INPUT:
|
|
1352
|
+
|
|
1353
|
+
- ``filename`` -- the file in which you want the problem
|
|
1354
|
+
to be written
|
|
1355
|
+
|
|
1356
|
+
EXAMPLES::
|
|
1357
|
+
|
|
1358
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1359
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1360
|
+
sage: p.set_objective(x[1] + x[2])
|
|
1361
|
+
sage: p.add_constraint(-3*x[1] + 2*x[2], max=2)
|
|
1362
|
+
sage: import tempfile
|
|
1363
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.lp') as f:
|
|
1364
|
+
....: p.write_lp(f.name)
|
|
1365
|
+
Writing problem data to ...
|
|
1366
|
+
9 lines were written
|
|
1367
|
+
|
|
1368
|
+
For more information about the LP file format :
|
|
1369
|
+
http://lpsolve.sourceforge.net/5.5/lp-format.htm
|
|
1370
|
+
"""
|
|
1371
|
+
self._backend.write_lp(filename)
|
|
1372
|
+
|
|
1373
|
+
def _backend_variable_value(self, v, tolerance):
|
|
1374
|
+
"""
|
|
1375
|
+
Return the value of a variable component in the backend.
|
|
1376
|
+
|
|
1377
|
+
INPUT:
|
|
1378
|
+
|
|
1379
|
+
- ``v`` -- a variable component
|
|
1380
|
+
|
|
1381
|
+
- ``tolerance`` -- ignored
|
|
1382
|
+
|
|
1383
|
+
EXAMPLES::
|
|
1384
|
+
|
|
1385
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1386
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1387
|
+
sage: p.set_objective(x[3] + 3*x[4] + x[5])
|
|
1388
|
+
sage: p.add_constraint(x[3] + x[4] + 2*x[5], max=2)
|
|
1389
|
+
sage: p.solve()
|
|
1390
|
+
6.0
|
|
1391
|
+
sage: p._backend_variable_value(x[4], 1)
|
|
1392
|
+
2.0
|
|
1393
|
+
sage: type(_)
|
|
1394
|
+
<class 'float'>
|
|
1395
|
+
"""
|
|
1396
|
+
return self._backend.get_variable_value(self._variables[v])
|
|
1397
|
+
|
|
1398
|
+
def _backend_variable_value_ZZ(self, v, tolerance):
|
|
1399
|
+
"""
|
|
1400
|
+
Return the value of a variable component in the backend as an integer.
|
|
1401
|
+
|
|
1402
|
+
The value is rounded to an integer, and if the difference to the
|
|
1403
|
+
original value is greater than ``tolerance``, raise a :exc:`RuntimeError`.
|
|
1404
|
+
|
|
1405
|
+
INPUT:
|
|
1406
|
+
|
|
1407
|
+
- ``v`` -- a variable component
|
|
1408
|
+
|
|
1409
|
+
- ``tolerance`` -- a nonnegative real number
|
|
1410
|
+
|
|
1411
|
+
OUTPUT: an element of ``ZZ``
|
|
1412
|
+
|
|
1413
|
+
EXAMPLES::
|
|
1414
|
+
|
|
1415
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1416
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1417
|
+
sage: p.set_objective(x[3] + 3*x[4] + x[5])
|
|
1418
|
+
sage: p.add_constraint(x[3] + x[4] + 2*x[5], max=2)
|
|
1419
|
+
sage: p.solve()
|
|
1420
|
+
6.0
|
|
1421
|
+
sage: p._backend_variable_value_ZZ(x[4], 0.01)
|
|
1422
|
+
2
|
|
1423
|
+
sage: _.parent()
|
|
1424
|
+
Integer Ring
|
|
1425
|
+
sage: p.add_constraint(3*x[4] <= 5)
|
|
1426
|
+
sage: p.solve()
|
|
1427
|
+
5.3333...
|
|
1428
|
+
sage: p._backend_variable_value_ZZ(x[4], 0.01)
|
|
1429
|
+
Traceback (most recent call last):
|
|
1430
|
+
...
|
|
1431
|
+
RuntimeError: variable x_1 exceeds integrality tolerance 0.0100...
|
|
1432
|
+
"""
|
|
1433
|
+
if tolerance is None:
|
|
1434
|
+
raise TypeError('for converting to integers, a tolerance must be provided')
|
|
1435
|
+
value = self._backend_variable_value(v, tolerance)
|
|
1436
|
+
value_ZZ = ZZ(round(value))
|
|
1437
|
+
if abs(value - value_ZZ) > tolerance:
|
|
1438
|
+
raise RuntimeError(f'variable {v} exceeds integrality tolerance {tolerance}')
|
|
1439
|
+
return value_ZZ
|
|
1440
|
+
|
|
1441
|
+
def _backend_variable_value_bool(self, v, tolerance):
|
|
1442
|
+
"""
|
|
1443
|
+
Return the value of a variable component in the backend as a boolean.
|
|
1444
|
+
|
|
1445
|
+
The value is rounded to an integer, and if the difference to the
|
|
1446
|
+
original value is greater than ``tolerance``, raise a :exc:`RuntimeError`.
|
|
1447
|
+
|
|
1448
|
+
If the rounded value is anything other than 0 or 1, also a
|
|
1449
|
+
:exc:`RuntimeError` is raised.
|
|
1450
|
+
|
|
1451
|
+
INPUT:
|
|
1452
|
+
|
|
1453
|
+
- ``v`` -- a variable component
|
|
1454
|
+
|
|
1455
|
+
- ``tolerance`` -- a nonnegative real number
|
|
1456
|
+
|
|
1457
|
+
OUTPUT: a ``bool``
|
|
1458
|
+
|
|
1459
|
+
EXAMPLES::
|
|
1460
|
+
|
|
1461
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1462
|
+
sage: x = p.new_variable(binary=True)
|
|
1463
|
+
sage: p.set_objective(x[3] + 3*x[4] + x[5])
|
|
1464
|
+
sage: p.add_constraint(x[3] + x[4] + 2*x[5], max=2)
|
|
1465
|
+
sage: p.solve()
|
|
1466
|
+
4.0
|
|
1467
|
+
sage: p._backend_variable_value_bool(x[4], 0.01)
|
|
1468
|
+
True
|
|
1469
|
+
sage: p._backend_variable_value_bool(x[5], 0.01)
|
|
1470
|
+
False
|
|
1471
|
+
"""
|
|
1472
|
+
value_ZZ = self._backend_variable_value_ZZ(v, tolerance)
|
|
1473
|
+
if value_ZZ not in (0, 1):
|
|
1474
|
+
raise RuntimeError(f'variable {v} is {value_ZZ} but should be 0 or 1')
|
|
1475
|
+
return bool(value_ZZ)
|
|
1476
|
+
|
|
1477
|
+
def _backend_variable_value_True(self, v, tolerance):
|
|
1478
|
+
"""
|
|
1479
|
+
Return the value of a variable component converted to the base ring or
|
|
1480
|
+
``ZZ``.
|
|
1481
|
+
|
|
1482
|
+
INPUT:
|
|
1483
|
+
|
|
1484
|
+
- ``v`` -- a variable component
|
|
1485
|
+
|
|
1486
|
+
- ``tolerance`` -- if ``v`` is declared integer or binary, a
|
|
1487
|
+
nonnegative real number; otherwise, ignored
|
|
1488
|
+
|
|
1489
|
+
OUTPUT:
|
|
1490
|
+
|
|
1491
|
+
An element of ``ZZ`` for MIP variables declared integer or binary, or
|
|
1492
|
+
an element of the :meth:`base_ring` otherwise.
|
|
1493
|
+
|
|
1494
|
+
EXAMPLES::
|
|
1495
|
+
|
|
1496
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1497
|
+
sage: x = p.new_variable(binary=True)
|
|
1498
|
+
sage: p.set_objective(x[3] + 3*x[4] + x[5])
|
|
1499
|
+
sage: p.add_constraint(x[3] + x[4] + 2*x[5], max=2)
|
|
1500
|
+
sage: p.solve()
|
|
1501
|
+
4.0
|
|
1502
|
+
sage: p._backend_variable_value_True(x[4], 0.01)
|
|
1503
|
+
1
|
|
1504
|
+
|
|
1505
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1506
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1507
|
+
sage: p.set_objective(x[3] + 3*x[4] + x[5])
|
|
1508
|
+
sage: p.add_constraint(x[3] + x[4] + 2*x[5], max=2)
|
|
1509
|
+
sage: p.add_constraint(3*x[4] <= 5)
|
|
1510
|
+
sage: p.solve()
|
|
1511
|
+
5.3333...
|
|
1512
|
+
sage: p._backend_variable_value_True(x[4], 0.01)
|
|
1513
|
+
1.6666...
|
|
1514
|
+
sage: _.parent()
|
|
1515
|
+
Real Double Field
|
|
1516
|
+
"""
|
|
1517
|
+
if self.is_binary(v) or self.is_integer(v):
|
|
1518
|
+
return self._backend_variable_value_ZZ(v, tolerance)
|
|
1519
|
+
return self.base_ring()(self._backend_variable_value(v, tolerance))
|
|
1520
|
+
|
|
1521
|
+
def get_values(self, *lists, convert=None, tolerance=None):
|
|
1522
|
+
r"""
|
|
1523
|
+
Return values found by the previous call to ``solve()``.
|
|
1524
|
+
|
|
1525
|
+
INPUT:
|
|
1526
|
+
|
|
1527
|
+
- ``*lists`` -- any instance of ``MIPVariable`` (or one of its
|
|
1528
|
+
elements), or lists of them
|
|
1529
|
+
|
|
1530
|
+
- ``convert`` -- (default: ``None``) ``ZZ``, ``bool``, or ``True``:
|
|
1531
|
+
|
|
1532
|
+
- if ``convert=None`` (default), return all variable values as the
|
|
1533
|
+
backend provides them, i.e., as an element of :meth:`base_ring` or a
|
|
1534
|
+
``float``.
|
|
1535
|
+
|
|
1536
|
+
- if ``convert=ZZ``, convert all variable values from the
|
|
1537
|
+
:meth:`base_ring` by rounding to the nearest integer.
|
|
1538
|
+
|
|
1539
|
+
- if ``convert=bool``, convert all variable values from the
|
|
1540
|
+
:meth:`base_ring` by rounding to 0/1 and converting to ``bool``.
|
|
1541
|
+
|
|
1542
|
+
- if ``convert=True``, use ``ZZ`` for MIP variables declared integer
|
|
1543
|
+
or binary, and convert the values of all other variables to the
|
|
1544
|
+
:meth:`base_ring`.
|
|
1545
|
+
|
|
1546
|
+
- ``tolerance`` -- ``None``, a positive real number, or ``0`` (if
|
|
1547
|
+
:meth:`base_ring` is an exact ring). Required if ``convert`` is not
|
|
1548
|
+
``None`` and any integer conversion is to be done. If the variable
|
|
1549
|
+
value differs from the nearest integer by more than ``tolerance``,
|
|
1550
|
+
raise a :exc:`RuntimeError`.
|
|
1551
|
+
|
|
1552
|
+
OUTPUT:
|
|
1553
|
+
|
|
1554
|
+
- Each instance of ``MIPVariable`` is replaced by a dictionary
|
|
1555
|
+
containing the numerical values found for each
|
|
1556
|
+
corresponding variable in the instance.
|
|
1557
|
+
- Each element of an instance of a ``MIPVariable`` is replaced
|
|
1558
|
+
by its corresponding numerical value.
|
|
1559
|
+
|
|
1560
|
+
.. NOTE::
|
|
1561
|
+
|
|
1562
|
+
While a variable may be declared as binary or integer, its value is
|
|
1563
|
+
an element of the :meth:`base_ring`, or for the numerical solvers,
|
|
1564
|
+
a ``float``.
|
|
1565
|
+
|
|
1566
|
+
For the numerical solvers, :meth:`base_ring` is ``RDF``, an inexact
|
|
1567
|
+
ring. Code using ``get_values`` should always account for possible
|
|
1568
|
+
numerical errors.
|
|
1569
|
+
|
|
1570
|
+
Even for variables declared as binary or integer, or known to be an
|
|
1571
|
+
integer because of the mathematical properties of the model, the
|
|
1572
|
+
returned values cannot be expected to be exact integers. This is
|
|
1573
|
+
normal behavior of the numerical solvers.
|
|
1574
|
+
|
|
1575
|
+
For correct operation, any user code needs to avoid exact
|
|
1576
|
+
comparisons (``==``, ``!=``) and instead allow for numerical
|
|
1577
|
+
tolerances. The magnitude of the numerical tolerances depends on
|
|
1578
|
+
both the model and the solver.
|
|
1579
|
+
|
|
1580
|
+
The arguments ``convert`` and ``tolerance`` facilitate writing
|
|
1581
|
+
correct code. See examples below.
|
|
1582
|
+
|
|
1583
|
+
EXAMPLES::
|
|
1584
|
+
|
|
1585
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1586
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1587
|
+
sage: y = p.new_variable(nonnegative=True)
|
|
1588
|
+
sage: p.set_objective(x[3] + 3*y[2,9] + x[5])
|
|
1589
|
+
sage: p.add_constraint(x[3] + y[2,9] + 2*x[5], max=2)
|
|
1590
|
+
sage: p.solve()
|
|
1591
|
+
6.0
|
|
1592
|
+
|
|
1593
|
+
To return the value of ``y[2,9]`` in the optimal solution::
|
|
1594
|
+
|
|
1595
|
+
sage: p.get_values(y[2,9])
|
|
1596
|
+
2.0
|
|
1597
|
+
sage: type(_)
|
|
1598
|
+
<class 'float'>
|
|
1599
|
+
|
|
1600
|
+
To convert the value to the :meth:`base_ring`::
|
|
1601
|
+
|
|
1602
|
+
sage: p.get_values(y[2,9], convert=True)
|
|
1603
|
+
2.0
|
|
1604
|
+
sage: _.parent()
|
|
1605
|
+
Real Double Field
|
|
1606
|
+
|
|
1607
|
+
To get a dictionary identical to ``x`` containing the
|
|
1608
|
+
values for the corresponding variables in the optimal solution::
|
|
1609
|
+
|
|
1610
|
+
sage: x_sol = p.get_values(x)
|
|
1611
|
+
sage: sorted(x_sol)
|
|
1612
|
+
[3, 5]
|
|
1613
|
+
|
|
1614
|
+
Obviously, it also works with variables of higher dimension::
|
|
1615
|
+
|
|
1616
|
+
sage: y_sol = p.get_values(y)
|
|
1617
|
+
|
|
1618
|
+
We could also have tried ::
|
|
1619
|
+
|
|
1620
|
+
sage: [x_sol, y_sol] = p.get_values(x, y)
|
|
1621
|
+
|
|
1622
|
+
Or::
|
|
1623
|
+
|
|
1624
|
+
sage: [x_sol, y_sol] = p.get_values([x, y])
|
|
1625
|
+
|
|
1626
|
+
Using ``convert`` and ``tolerance``. First, a binary knapsack::
|
|
1627
|
+
|
|
1628
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
1629
|
+
sage: x = p.new_variable(binary=True)
|
|
1630
|
+
sage: p.set_objective(3*x[1] + 4*x[2] + 5*x[3])
|
|
1631
|
+
sage: p.add_constraint(2*x[1] + 3*x[2] + 4*x[3] <= 6)
|
|
1632
|
+
sage: p.solve()
|
|
1633
|
+
8.0
|
|
1634
|
+
sage: x_opt = p.get_values(x); x_opt
|
|
1635
|
+
{1: 1.0, 2: 0.0, 3: 1.0}
|
|
1636
|
+
sage: type(x_opt[1])
|
|
1637
|
+
<class 'float'>
|
|
1638
|
+
sage: x_opt_ZZ = p.get_values(x, convert=True, tolerance=1e-6); x_opt_ZZ
|
|
1639
|
+
{1: 1, 2: 0, 3: 1}
|
|
1640
|
+
sage: x_opt_ZZ[1].parent()
|
|
1641
|
+
Integer Ring
|
|
1642
|
+
sage: x_opt_bool = p.get_values(x, convert=bool, tolerance=1e-6); x_opt_bool
|
|
1643
|
+
{1: True, 2: False, 3: True}
|
|
1644
|
+
|
|
1645
|
+
Thanks to total unimodularity, single-commodity network flow problems
|
|
1646
|
+
with integer capacities and integer supplies/demands have integer vertex
|
|
1647
|
+
solutions. Hence the integrality of solutions is mathematically
|
|
1648
|
+
guaranteed in an optimal solution if we use the simplex algorithm. A
|
|
1649
|
+
numerical LP solver based on the simplex method such as GLPK will return
|
|
1650
|
+
an integer solution only up to a numerical error. Hence, for correct
|
|
1651
|
+
operation, we should use ``tolerance``::
|
|
1652
|
+
|
|
1653
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK', maximization=False)
|
|
1654
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1655
|
+
sage: x.set_max(1)
|
|
1656
|
+
sage: p.add_constraint(x['sa'] + x['sb'] == 1)
|
|
1657
|
+
sage: p.add_constraint(x['sa'] + x['ba'] - x['ab'] - x['at'] == 0)
|
|
1658
|
+
sage: p.add_constraint(x['sb'] + x['ab'] - x['ba'] - x['bt'] == 0)
|
|
1659
|
+
sage: p.set_objective(10*x['sa'] + 10*x['bt'])
|
|
1660
|
+
sage: p.solve()
|
|
1661
|
+
0.0
|
|
1662
|
+
sage: x_opt = p.get_values(x); x_opt
|
|
1663
|
+
{'ab': 0.0, 'at': 1.0, 'ba': 1.0, 'bt': -0.0, 'sa': 0.0, 'sb': 1.0}
|
|
1664
|
+
sage: x_opt_ZZ = p.get_values(x, convert=ZZ, tolerance=1e-6); x_opt_ZZ
|
|
1665
|
+
{'ab': 0, 'at': 1, 'ba': 1, 'bt': 0, 'sa': 0, 'sb': 1}
|
|
1666
|
+
|
|
1667
|
+
TESTS:
|
|
1668
|
+
|
|
1669
|
+
Test that an error is reported when we try to get the value
|
|
1670
|
+
of something that is not a variable in this problem::
|
|
1671
|
+
|
|
1672
|
+
sage: p.get_values("Something strange")
|
|
1673
|
+
Traceback (most recent call last):
|
|
1674
|
+
...
|
|
1675
|
+
TypeError: Not a MIPVariable: ...
|
|
1676
|
+
sage: p.get_values("Something stranger", 4711)
|
|
1677
|
+
Traceback (most recent call last):
|
|
1678
|
+
...
|
|
1679
|
+
TypeError: Not a MIPVariable: ...
|
|
1680
|
+
sage: M1 = MixedIntegerLinearProgram(solver='GLPK')
|
|
1681
|
+
sage: M2 = MixedIntegerLinearProgram(solver='GLPK')
|
|
1682
|
+
sage: x = M1.new_variable()
|
|
1683
|
+
sage: y = M1.new_variable()
|
|
1684
|
+
sage: z = M2.new_variable()
|
|
1685
|
+
sage: M2.add_constraint(z[0] <= 5)
|
|
1686
|
+
sage: M2.solve()
|
|
1687
|
+
0.0
|
|
1688
|
+
sage: M2.get_values(x)
|
|
1689
|
+
Traceback (most recent call last):
|
|
1690
|
+
...
|
|
1691
|
+
ValueError: ...
|
|
1692
|
+
sage: M2.get_values(y)
|
|
1693
|
+
Traceback (most recent call last):
|
|
1694
|
+
...
|
|
1695
|
+
ValueError: ...
|
|
1696
|
+
|
|
1697
|
+
Test input validation for ``convert`` and ``tolerance``::
|
|
1698
|
+
|
|
1699
|
+
sage: M_inexact = MixedIntegerLinearProgram(solver='GLPK')
|
|
1700
|
+
sage: x = M_inexact.new_variable(binary=True)
|
|
1701
|
+
sage: x[1]
|
|
1702
|
+
x_0
|
|
1703
|
+
sage: M_inexact.solve()
|
|
1704
|
+
0.0
|
|
1705
|
+
sage: M_inexact.get_values(tolerance=0.01)
|
|
1706
|
+
Traceback (most recent call last):
|
|
1707
|
+
...
|
|
1708
|
+
TypeError: cannot use tolerance if convert is None
|
|
1709
|
+
sage: M_inexact.get_values(x[1], convert=True)
|
|
1710
|
+
Traceback (most recent call last):
|
|
1711
|
+
...
|
|
1712
|
+
TypeError: for converting to integers, a tolerance must be provided
|
|
1713
|
+
sage: M_inexact.get_values(convert=True, tolerance=0)
|
|
1714
|
+
Traceback (most recent call last):
|
|
1715
|
+
...
|
|
1716
|
+
ValueError: for an inexact base_ring, tolerance must be positive
|
|
1717
|
+
|
|
1718
|
+
sage: M_exact = MixedIntegerLinearProgram(solver='ppl')
|
|
1719
|
+
sage: x = M_exact.new_variable(binary=True)
|
|
1720
|
+
sage: x[1]
|
|
1721
|
+
x_0
|
|
1722
|
+
sage: M_exact.solve()
|
|
1723
|
+
0
|
|
1724
|
+
sage: M_exact.get_values(convert=True)
|
|
1725
|
+
[]
|
|
1726
|
+
sage: M_exact.get_values(x[1], convert=True)
|
|
1727
|
+
Traceback (most recent call last):
|
|
1728
|
+
...
|
|
1729
|
+
TypeError: for converting to integers, a tolerance must be provided
|
|
1730
|
+
sage: M_exact.get_values(x[1], convert=True, tolerance=0)
|
|
1731
|
+
0
|
|
1732
|
+
sage: M_exact.get_values(convert=True, tolerance=-0.2)
|
|
1733
|
+
Traceback (most recent call last):
|
|
1734
|
+
...
|
|
1735
|
+
ValueError: for an exact base_ring, tolerance must be nonnegative
|
|
1736
|
+
"""
|
|
1737
|
+
if convert is None:
|
|
1738
|
+
if tolerance is not None:
|
|
1739
|
+
raise TypeError('cannot use tolerance if convert is None')
|
|
1740
|
+
get_backend_variable_value = self._backend_variable_value
|
|
1741
|
+
else:
|
|
1742
|
+
if tolerance is not None:
|
|
1743
|
+
if self.base_ring().is_exact():
|
|
1744
|
+
if tolerance < 0:
|
|
1745
|
+
raise ValueError('for an exact base_ring, tolerance must be nonnegative')
|
|
1746
|
+
else:
|
|
1747
|
+
if tolerance <= 0:
|
|
1748
|
+
raise ValueError('for an inexact base_ring, tolerance must be positive')
|
|
1749
|
+
if convert is ZZ:
|
|
1750
|
+
get_backend_variable_value = self._backend_variable_value_ZZ
|
|
1751
|
+
elif convert is bool:
|
|
1752
|
+
get_backend_variable_value = self._backend_variable_value_bool
|
|
1753
|
+
elif convert is True:
|
|
1754
|
+
get_backend_variable_value = self._backend_variable_value_True
|
|
1755
|
+
else:
|
|
1756
|
+
raise ValueError('convert should be one of None, ZZ, boolean, True')
|
|
1757
|
+
|
|
1758
|
+
val = []
|
|
1759
|
+
for l in lists:
|
|
1760
|
+
if isinstance(l, MIPVariable):
|
|
1761
|
+
if self != l.mip():
|
|
1762
|
+
raise ValueError("Variable {!r} is a variable from a different problem".format(l))
|
|
1763
|
+
c = {}
|
|
1764
|
+
for (k,v) in l.items():
|
|
1765
|
+
c[k] = get_backend_variable_value(v, tolerance)
|
|
1766
|
+
val.append(c)
|
|
1767
|
+
elif isinstance(l, list):
|
|
1768
|
+
if len(l) == 1:
|
|
1769
|
+
val.append([self.get_values(l[0], convert=convert, tolerance=tolerance)])
|
|
1770
|
+
else:
|
|
1771
|
+
c = []
|
|
1772
|
+
[c.append(self.get_values(ll, convert=convert, tolerance=tolerance)) for ll in l]
|
|
1773
|
+
val.append(c)
|
|
1774
|
+
elif l in self._variables:
|
|
1775
|
+
val.append(get_backend_variable_value(l, tolerance))
|
|
1776
|
+
else:
|
|
1777
|
+
raise TypeError("Not a MIPVariable: {!r}".format(l))
|
|
1778
|
+
|
|
1779
|
+
if len(lists) == 1:
|
|
1780
|
+
return val[0]
|
|
1781
|
+
else:
|
|
1782
|
+
return val
|
|
1783
|
+
|
|
1784
|
+
def set_objective(self, obj):
|
|
1785
|
+
r"""
|
|
1786
|
+
Set the objective of the ``MixedIntegerLinearProgram``.
|
|
1787
|
+
|
|
1788
|
+
INPUT:
|
|
1789
|
+
|
|
1790
|
+
- ``obj`` -- a linear function to be optimized
|
|
1791
|
+
( can also be set to ``None`` or ``0`` or any number when just
|
|
1792
|
+
looking for a feasible solution )
|
|
1793
|
+
|
|
1794
|
+
EXAMPLES:
|
|
1795
|
+
|
|
1796
|
+
Let's solve the following linear program::
|
|
1797
|
+
|
|
1798
|
+
Maximize:
|
|
1799
|
+
x + 5 * y
|
|
1800
|
+
Constraints:
|
|
1801
|
+
x + 0.2 y <= 4
|
|
1802
|
+
1.5 * x + 3 * y <= 4
|
|
1803
|
+
Variables:
|
|
1804
|
+
x is Real (min = 0, max = None)
|
|
1805
|
+
y is Real (min = 0, max = None)
|
|
1806
|
+
|
|
1807
|
+
This linear program can be solved as follows::
|
|
1808
|
+
|
|
1809
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1810
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1811
|
+
sage: p.set_objective(x[1] + 5*x[2])
|
|
1812
|
+
sage: p.add_constraint(x[1] + 2/10*x[2], max=4)
|
|
1813
|
+
sage: p.add_constraint(1.5*x[1] + 3*x[2], max=4)
|
|
1814
|
+
sage: round(p.solve(),5)
|
|
1815
|
+
6.66667
|
|
1816
|
+
sage: p.set_objective(None)
|
|
1817
|
+
sage: _ = p.solve()
|
|
1818
|
+
|
|
1819
|
+
TESTS:
|
|
1820
|
+
|
|
1821
|
+
Test whether numbers as constant objective functions are accepted::
|
|
1822
|
+
|
|
1823
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1824
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1825
|
+
sage: p.set_objective(42)
|
|
1826
|
+
sage: p.solve() # tol 1e-8
|
|
1827
|
+
42
|
|
1828
|
+
"""
|
|
1829
|
+
cdef list values = []
|
|
1830
|
+
|
|
1831
|
+
# If the objective is None, or a constant, we want to remember
|
|
1832
|
+
# that the objective function has been defined ( the user did not
|
|
1833
|
+
# forget it ). In some LP problems, you just want a feasible solution
|
|
1834
|
+
# and do not care about any function being optimal.
|
|
1835
|
+
cdef int i
|
|
1836
|
+
|
|
1837
|
+
if obj is None:
|
|
1838
|
+
f = {-1: 0}
|
|
1839
|
+
else:
|
|
1840
|
+
# See if it is a constant
|
|
1841
|
+
R = self.base_ring()
|
|
1842
|
+
try:
|
|
1843
|
+
f = {-1: R(obj)}
|
|
1844
|
+
except TypeError:
|
|
1845
|
+
# Should be a linear function
|
|
1846
|
+
f = obj.dict()
|
|
1847
|
+
d = f.pop(-1,self._backend.zero())
|
|
1848
|
+
|
|
1849
|
+
for i in range(self._backend.ncols()):
|
|
1850
|
+
values.append(f.get(i,self._backend.zero()))
|
|
1851
|
+
self._backend.set_objective(values, d)
|
|
1852
|
+
|
|
1853
|
+
def add_constraint(self, linear_function, max=None, min=None, name=None,
|
|
1854
|
+
return_indices=False):
|
|
1855
|
+
r"""
|
|
1856
|
+
Add a constraint to the ``MixedIntegerLinearProgram``.
|
|
1857
|
+
|
|
1858
|
+
INPUT:
|
|
1859
|
+
|
|
1860
|
+
- ``linear_function`` -- four different types of arguments are
|
|
1861
|
+
admissible:
|
|
1862
|
+
|
|
1863
|
+
- A linear function. In this case, one of the arguments
|
|
1864
|
+
``min`` or ``max`` has to be specified.
|
|
1865
|
+
|
|
1866
|
+
- A linear constraint of the form ``A <= B``, ``A >= B``,
|
|
1867
|
+
``A <= B <= C``, ``A >= B >= C`` or ``A == B``.
|
|
1868
|
+
|
|
1869
|
+
- A vector-valued linear function, see
|
|
1870
|
+
:mod:`~sage.numerical.linear_tensor`. In this case, one
|
|
1871
|
+
of the arguments ``min`` or ``max`` has to be specified.
|
|
1872
|
+
|
|
1873
|
+
- An (in)equality of vector-valued linear functions, that
|
|
1874
|
+
is, elements of the space of linear functions tensored
|
|
1875
|
+
with a vector space. See
|
|
1876
|
+
:mod:`~sage.numerical.linear_tensor_constraints` for
|
|
1877
|
+
details.
|
|
1878
|
+
|
|
1879
|
+
- ``max`` -- constant or ``None`` (default). An upper bound on
|
|
1880
|
+
the linear function. This must be a numerical value for
|
|
1881
|
+
scalar linear functions, or a vector for vector-valued
|
|
1882
|
+
linear functions. Not allowed if the ``linear_function``
|
|
1883
|
+
argument is a symbolic (in)-equality.
|
|
1884
|
+
|
|
1885
|
+
- ``min`` -- constant or ``None`` (default). A lower bound on
|
|
1886
|
+
the linear function. This must be a numerical value for
|
|
1887
|
+
scalar linear functions, or a vector for vector-valued
|
|
1888
|
+
linear functions. Not allowed if the ``linear_function``
|
|
1889
|
+
argument is a symbolic (in)-equality.
|
|
1890
|
+
|
|
1891
|
+
- ``name`` -- a name for the constraint
|
|
1892
|
+
|
|
1893
|
+
- ``return_indices`` -- boolean (default: ``False``),
|
|
1894
|
+
whether to return the indices of the added constraints
|
|
1895
|
+
|
|
1896
|
+
OUTPUT:
|
|
1897
|
+
|
|
1898
|
+
The row indices of the constraints added, if
|
|
1899
|
+
``return_indices`` is true and the backend guarantees that
|
|
1900
|
+
removing them again yields the original MIP, ``None``
|
|
1901
|
+
otherwise.
|
|
1902
|
+
|
|
1903
|
+
To set a lower and/or upper bound on the variables use the methods
|
|
1904
|
+
``set_min`` and/or ``set_max`` of ``MixedIntegerLinearProgram``.
|
|
1905
|
+
|
|
1906
|
+
EXAMPLES:
|
|
1907
|
+
|
|
1908
|
+
Consider the following linear program::
|
|
1909
|
+
|
|
1910
|
+
Maximize:
|
|
1911
|
+
x + 5 * y
|
|
1912
|
+
Constraints:
|
|
1913
|
+
x + 0.2 y <= 4
|
|
1914
|
+
1.5 * x + 3 * y <= 4
|
|
1915
|
+
Variables:
|
|
1916
|
+
x is Real (min = 0, max = None)
|
|
1917
|
+
y is Real (min = 0, max = None)
|
|
1918
|
+
|
|
1919
|
+
It can be solved as follows::
|
|
1920
|
+
|
|
1921
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1922
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1923
|
+
sage: p.set_objective(x[0] + 5*x[1])
|
|
1924
|
+
sage: p.add_constraint(x[0] + 0.2*x[1], max=4)
|
|
1925
|
+
sage: p.add_constraint(1.5*x[0] + 3*x[1], max=4)
|
|
1926
|
+
sage: p.solve() # rel tol 1e-15
|
|
1927
|
+
6.666666666666666
|
|
1928
|
+
|
|
1929
|
+
There are two different ways to add the constraint
|
|
1930
|
+
``x[5] + 3*x[7] <= x[6] + 3`` to a ``MixedIntegerLinearProgram``.
|
|
1931
|
+
|
|
1932
|
+
The first one consists in giving ``add_constraint`` this
|
|
1933
|
+
very expression::
|
|
1934
|
+
|
|
1935
|
+
sage: p.add_constraint(x[5] + 3*x[7] <= x[6] + 3)
|
|
1936
|
+
|
|
1937
|
+
The second (slightly more efficient) one is to use the
|
|
1938
|
+
arguments ``min`` or ``max``, which can only be numerical
|
|
1939
|
+
values::
|
|
1940
|
+
|
|
1941
|
+
sage: p.add_constraint(x[5] + 3*x[7] - x[6], max=3)
|
|
1942
|
+
|
|
1943
|
+
One can also define double-bounds or equality using symbols
|
|
1944
|
+
``<=``, ``>=`` and ``==``::
|
|
1945
|
+
|
|
1946
|
+
sage: p.add_constraint(x[5] + 3*x[7] == x[6] + 3)
|
|
1947
|
+
sage: p.add_constraint(x[5] + 3*x[7] <= x[6] + 3 <= x[8] + 27)
|
|
1948
|
+
|
|
1949
|
+
Using this notation, the previous program can be written as::
|
|
1950
|
+
|
|
1951
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1952
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1953
|
+
sage: p.set_objective(x[0] + 5*x[1])
|
|
1954
|
+
sage: p.add_constraint(x[0] + 0.2*x[1] <= 4)
|
|
1955
|
+
sage: p.add_constraint(1.5*x[0] + 3*x[1] <= 4)
|
|
1956
|
+
sage: p.solve() # rel tol 1e-15
|
|
1957
|
+
6.666666666666666
|
|
1958
|
+
|
|
1959
|
+
The two constraints can also be combined into a single
|
|
1960
|
+
vector-valued constraint::
|
|
1961
|
+
|
|
1962
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1963
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1964
|
+
sage: p.set_objective(x[0] + 5*x[1])
|
|
1965
|
+
sage: f_vec = vector([1, 1.5]) * x[0] + vector([0.2, 3]) * x[1]; f_vec
|
|
1966
|
+
(1.0, 1.5)*x_0 + (0.2, 3.0)*x_1
|
|
1967
|
+
sage: p.add_constraint(f_vec, max=vector([4, 4]))
|
|
1968
|
+
sage: p.solve() # rel tol 1e-15
|
|
1969
|
+
6.666666666666666
|
|
1970
|
+
|
|
1971
|
+
Instead of specifying the maximum in the optional ``max``
|
|
1972
|
+
argument, we can also use (in)equality notation for
|
|
1973
|
+
vector-valued linear functions::
|
|
1974
|
+
|
|
1975
|
+
sage: f_vec <= 4 # constant rhs becomes vector
|
|
1976
|
+
(1.0, 1.5)*x_0 + (0.2, 3.0)*x_1 <= (4.0, 4.0)
|
|
1977
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1978
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1979
|
+
sage: p.set_objective(x[0] + 5*x[1])
|
|
1980
|
+
sage: p.add_constraint(f_vec <= 4)
|
|
1981
|
+
sage: p.solve() # rel tol 1e-15
|
|
1982
|
+
6.666666666666666
|
|
1983
|
+
|
|
1984
|
+
Finally, one can use the matrix * :class:`MIPVariable`
|
|
1985
|
+
notation to write vector-valued linear functions::
|
|
1986
|
+
|
|
1987
|
+
sage: m = matrix([[1.0, 0.2], [1.5, 3.0]]); m
|
|
1988
|
+
[ 1.00000000000000 0.200000000000000]
|
|
1989
|
+
[ 1.50000000000000 3.00000000000000]
|
|
1990
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
1991
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
1992
|
+
sage: p.set_objective(x[0] + 5*x[1])
|
|
1993
|
+
sage: p.add_constraint(m * x <= 4)
|
|
1994
|
+
sage: p.solve() # rel tol 1e-15
|
|
1995
|
+
6.666666666666666
|
|
1996
|
+
|
|
1997
|
+
TESTS:
|
|
1998
|
+
|
|
1999
|
+
Complex constraints::
|
|
2000
|
+
|
|
2001
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2002
|
+
sage: b = p.new_variable(nonnegative=True)
|
|
2003
|
+
sage: p.add_constraint(b[8] - b[15] <= 3*b[8] + 9)
|
|
2004
|
+
sage: p.show()
|
|
2005
|
+
Maximization:
|
|
2006
|
+
<BLANKLINE>
|
|
2007
|
+
Constraints:
|
|
2008
|
+
-2.0 x_0 - x_1 <= 9.0
|
|
2009
|
+
Variables:
|
|
2010
|
+
x_0 is a continuous variable (min=0.0, max=+oo)
|
|
2011
|
+
x_1 is a continuous variable (min=0.0, max=+oo)
|
|
2012
|
+
|
|
2013
|
+
Trivially true empty constraint:
|
|
2014
|
+
|
|
2015
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2016
|
+
sage: p.add_constraint(sum([]), max=2)
|
|
2017
|
+
sage: p.solve()
|
|
2018
|
+
0.0
|
|
2019
|
+
|
|
2020
|
+
Infeasible empty constraint::
|
|
2021
|
+
|
|
2022
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2023
|
+
sage: p.add_constraint(sum([]), min=2)
|
|
2024
|
+
sage: p.solve()
|
|
2025
|
+
Traceback (most recent call last):
|
|
2026
|
+
...
|
|
2027
|
+
MIPSolverException: GLPK: Problem has no feasible solution
|
|
2028
|
+
|
|
2029
|
+
Min/Max are numerical ::
|
|
2030
|
+
|
|
2031
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2032
|
+
sage: p.add_constraint(v[3] + v[5], min = v[6])
|
|
2033
|
+
Traceback (most recent call last):
|
|
2034
|
+
...
|
|
2035
|
+
ValueError: min and max arguments are required to be constants
|
|
2036
|
+
sage: p.add_constraint(v[3] + v[5], max = v[6])
|
|
2037
|
+
Traceback (most recent call last):
|
|
2038
|
+
...
|
|
2039
|
+
ValueError: min and max arguments are required to be constants
|
|
2040
|
+
|
|
2041
|
+
Do not add redundant elements (notice only one copy of each constraint is added)::
|
|
2042
|
+
|
|
2043
|
+
sage: lp = MixedIntegerLinearProgram(solver='GLPK', check_redundant=True)
|
|
2044
|
+
sage: for each in range(10):
|
|
2045
|
+
....: lp.add_constraint(lp[0]-lp[1], min=1)
|
|
2046
|
+
sage: lp.show()
|
|
2047
|
+
Maximization:
|
|
2048
|
+
<BLANKLINE>
|
|
2049
|
+
Constraints:
|
|
2050
|
+
1.0 <= x_0 - x_1
|
|
2051
|
+
Variables:
|
|
2052
|
+
x_0 is a continuous variable (min=-oo, max=+oo)
|
|
2053
|
+
x_1 is a continuous variable (min=-oo, max=+oo)
|
|
2054
|
+
|
|
2055
|
+
We check for constant multiples of constraints as well::
|
|
2056
|
+
|
|
2057
|
+
sage: for each in range(10):
|
|
2058
|
+
....: lp.add_constraint(2*lp[0] - 2*lp[1], min=2)
|
|
2059
|
+
sage: lp.show()
|
|
2060
|
+
Maximization:
|
|
2061
|
+
<BLANKLINE>
|
|
2062
|
+
Constraints:
|
|
2063
|
+
1.0 <= x_0 - x_1
|
|
2064
|
+
Variables:
|
|
2065
|
+
x_0 is a continuous variable (min=-oo, max=+oo)
|
|
2066
|
+
x_1 is a continuous variable (min=-oo, max=+oo)
|
|
2067
|
+
|
|
2068
|
+
But if the constant multiple is negative, we should add it anyway (once)::
|
|
2069
|
+
|
|
2070
|
+
sage: for each in range(10):
|
|
2071
|
+
....: lp.add_constraint(-2*lp[0] + 2*lp[1], min=-2)
|
|
2072
|
+
sage: lp.show()
|
|
2073
|
+
Maximization:
|
|
2074
|
+
<BLANKLINE>
|
|
2075
|
+
Constraints:
|
|
2076
|
+
1.0 <= x_0 - x_1
|
|
2077
|
+
-2.0 <= -2.0 x_0 + 2.0 x_1
|
|
2078
|
+
Variables:
|
|
2079
|
+
x_0 is a continuous variable (min=-oo, max=+oo)
|
|
2080
|
+
x_1 is a continuous variable (min=-oo, max=+oo)
|
|
2081
|
+
|
|
2082
|
+
Catch ``True`` / ``False`` as INPUT (:issue:`13646`)::
|
|
2083
|
+
|
|
2084
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2085
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2086
|
+
sage: p.add_constraint(True)
|
|
2087
|
+
Traceback (most recent call last):
|
|
2088
|
+
...
|
|
2089
|
+
ValueError: argument must be a linear function or constraint, got True
|
|
2090
|
+
|
|
2091
|
+
Check that adding and removing constraints works::
|
|
2092
|
+
|
|
2093
|
+
sage: p = MixedIntegerLinearProgram(check_redundant=True)
|
|
2094
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2095
|
+
sage: p.add_constraint(x[0] + x[1] <= 3)
|
|
2096
|
+
sage: p.set_objective(x[0] + 2*x[1])
|
|
2097
|
+
sage: p.solve()
|
|
2098
|
+
6.0
|
|
2099
|
+
|
|
2100
|
+
We add (non-trivially) redundant constraints::
|
|
2101
|
+
|
|
2102
|
+
sage: c1 = p.add_constraint(0 <= x[0] <= 3, return_indices=True); c1
|
|
2103
|
+
[1, 2]
|
|
2104
|
+
sage: p.solve()
|
|
2105
|
+
6.0
|
|
2106
|
+
|
|
2107
|
+
We add a non-redundant constraint::
|
|
2108
|
+
|
|
2109
|
+
sage: c2 = p.add_constraint(1 <= x[1] <= 2, return_indices=True); c2
|
|
2110
|
+
[3, 4]
|
|
2111
|
+
sage: p.solve()
|
|
2112
|
+
5.0
|
|
2113
|
+
|
|
2114
|
+
We remove the redundant constraints `1` and `2`, keep in mind
|
|
2115
|
+
that indices change when removing constraints::
|
|
2116
|
+
|
|
2117
|
+
sage: p.remove_constraint(1)
|
|
2118
|
+
sage: p.remove_constraint(1)
|
|
2119
|
+
sage: p.solve()
|
|
2120
|
+
5.0
|
|
2121
|
+
|
|
2122
|
+
We remove another constraint::
|
|
2123
|
+
|
|
2124
|
+
sage: p.remove_constraint(2)
|
|
2125
|
+
sage: p.solve()
|
|
2126
|
+
6.0
|
|
2127
|
+
"""
|
|
2128
|
+
from sage.numerical.linear_functions import LinearFunction, LinearConstraint
|
|
2129
|
+
from sage.numerical.linear_tensor import LinearTensor
|
|
2130
|
+
from sage.numerical.linear_tensor_constraints import LinearTensorConstraint
|
|
2131
|
+
if isinstance(linear_function, (LinearFunction, LinearTensor)):
|
|
2132
|
+
# Find the parent for the coefficients
|
|
2133
|
+
if isinstance(linear_function, LinearFunction):
|
|
2134
|
+
M = linear_function.parent().base_ring()
|
|
2135
|
+
elif isinstance(linear_function, LinearTensor):
|
|
2136
|
+
if not linear_function.parent().is_vector_space():
|
|
2137
|
+
raise ValueError('the linear function must be vector-valued')
|
|
2138
|
+
M = linear_function.parent().free_module()
|
|
2139
|
+
else:
|
|
2140
|
+
assert False, 'unreachable'
|
|
2141
|
+
# Normalize min/max
|
|
2142
|
+
try:
|
|
2143
|
+
min = None if min is None else M(min)
|
|
2144
|
+
max = None if max is None else M(max)
|
|
2145
|
+
except (ValueError, TypeError):
|
|
2146
|
+
raise ValueError("min and max arguments are required to be constants")
|
|
2147
|
+
if min is None and max is None:
|
|
2148
|
+
raise ValueError('at least one of "max" or "min" must be set')
|
|
2149
|
+
# Shift constant away
|
|
2150
|
+
constraint = copy(linear_function.dict())
|
|
2151
|
+
try:
|
|
2152
|
+
constant_coefficient = constraint.pop(-1)
|
|
2153
|
+
max = (max - constant_coefficient) if max is not None else None
|
|
2154
|
+
min = (min - constant_coefficient) if min is not None else None
|
|
2155
|
+
except KeyError:
|
|
2156
|
+
pass
|
|
2157
|
+
# Send to backend
|
|
2158
|
+
if isinstance(linear_function, LinearFunction):
|
|
2159
|
+
if self._check_redundant and self._is_redundant_constraint(constraint, min, max):
|
|
2160
|
+
if return_indices:
|
|
2161
|
+
return []
|
|
2162
|
+
return
|
|
2163
|
+
nrows_before = self._backend.nrows()
|
|
2164
|
+
self._backend.add_linear_constraint(constraint.items(), min, max, name)
|
|
2165
|
+
elif isinstance(linear_function, LinearTensor):
|
|
2166
|
+
nrows_before = self._backend.nrows()
|
|
2167
|
+
self._backend.add_linear_constraint_vector(M.degree(), constraint.items(), min, max, name)
|
|
2168
|
+
else:
|
|
2169
|
+
assert False, 'unreachable'
|
|
2170
|
+
if return_indices:
|
|
2171
|
+
return list(range(nrows_before, self._backend.nrows()))
|
|
2172
|
+
return
|
|
2173
|
+
elif isinstance(linear_function, LinearConstraint):
|
|
2174
|
+
if not (min is None and max is None):
|
|
2175
|
+
raise ValueError('min and max must not be specified for (in)equalities')
|
|
2176
|
+
relation = linear_function
|
|
2177
|
+
if return_indices:
|
|
2178
|
+
row_indices = []
|
|
2179
|
+
else:
|
|
2180
|
+
row_indices = None
|
|
2181
|
+
for lhs, rhs in relation.equations():
|
|
2182
|
+
new_indices = self.add_constraint(lhs-rhs, min=0, max=0,
|
|
2183
|
+
name=name,
|
|
2184
|
+
return_indices=return_indices)
|
|
2185
|
+
if new_indices is not None:
|
|
2186
|
+
row_indices.extend(new_indices)
|
|
2187
|
+
for lhs, rhs in relation.inequalities():
|
|
2188
|
+
new_indices = self.add_constraint(lhs-rhs, max=0,
|
|
2189
|
+
name=name,
|
|
2190
|
+
return_indices=return_indices)
|
|
2191
|
+
if new_indices is not None:
|
|
2192
|
+
row_indices.extend(new_indices)
|
|
2193
|
+
return row_indices
|
|
2194
|
+
elif isinstance(linear_function, LinearTensorConstraint):
|
|
2195
|
+
if not (min is None and max is None):
|
|
2196
|
+
raise ValueError('min and max must not be specified for (in)equalities')
|
|
2197
|
+
relation = linear_function
|
|
2198
|
+
M = relation.parent().linear_tensors().free_module()
|
|
2199
|
+
return self.add_constraint(relation.lhs() - relation.rhs(),
|
|
2200
|
+
min=M(0) if relation.is_equation() else None,
|
|
2201
|
+
max=M(0),
|
|
2202
|
+
name=name, return_indices=return_indices)
|
|
2203
|
+
elif isinstance(linear_function, bool):
|
|
2204
|
+
raise ValueError('argument must be a linear function or constraint, got ' + str(linear_function))
|
|
2205
|
+
else:
|
|
2206
|
+
try:
|
|
2207
|
+
linear_function = self.linear_functions_parent()(linear_function)
|
|
2208
|
+
except (TypeError, ValueError):
|
|
2209
|
+
raise ValueError('argument must be a linear function or constraint, got ' + str(linear_function))
|
|
2210
|
+
return self.add_constraint(linear_function, max=max, min=min,
|
|
2211
|
+
name=name, return_indices=return_indices)
|
|
2212
|
+
|
|
2213
|
+
def _is_redundant_constraint(self, constraint, min_bound, max_bound):
|
|
2214
|
+
"""
|
|
2215
|
+
Check whether the (scalar) constraint is redundant.
|
|
2216
|
+
|
|
2217
|
+
INPUT:
|
|
2218
|
+
|
|
2219
|
+
- ``constraint`` -- dictionary of a nonzero linear function
|
|
2220
|
+
without constant term
|
|
2221
|
+
|
|
2222
|
+
- ``min_bound``, ``max_bound`` -- base ring elements or
|
|
2223
|
+
``None``; the lower and upper bound
|
|
2224
|
+
|
|
2225
|
+
OUTPUT: boolean; whether the (normalized) constraint has already been added
|
|
2226
|
+
|
|
2227
|
+
EXAMPLES::
|
|
2228
|
+
|
|
2229
|
+
sage: mip.<x> = MixedIntegerLinearProgram(check_redundant=True, solver='GLPK')
|
|
2230
|
+
sage: mip.add_constraint(x[0], min=1)
|
|
2231
|
+
sage: mip._is_redundant_constraint((x[0]).dict(), 1, None)
|
|
2232
|
+
True
|
|
2233
|
+
sage: mip._is_redundant_constraint((-2*x[0]).dict(), None, -2)
|
|
2234
|
+
True
|
|
2235
|
+
sage: mip._is_redundant_constraint((x[1]).dict(), 1, None)
|
|
2236
|
+
False
|
|
2237
|
+
"""
|
|
2238
|
+
assert self._constraints is not None, 'must be initialized with check_redundant=True'
|
|
2239
|
+
assert -1 not in constraint, 'no constant term allowed'
|
|
2240
|
+
i0 = min([i for i, c in constraint.items() if c != 0])
|
|
2241
|
+
rescale = constraint[i0]
|
|
2242
|
+
constraint = tuple((i, c/rescale) for i, c in constraint.items())
|
|
2243
|
+
if rescale > 0:
|
|
2244
|
+
min_scaled = min_bound/rescale if min_bound is not None else None
|
|
2245
|
+
max_scaled = max_bound/rescale if max_bound is not None else None
|
|
2246
|
+
else:
|
|
2247
|
+
min_scaled = max_bound/rescale if max_bound is not None else None
|
|
2248
|
+
max_scaled = min_bound/rescale if min_bound is not None else None
|
|
2249
|
+
key = (constraint, min_scaled, max_scaled)
|
|
2250
|
+
if key in self._constraints:
|
|
2251
|
+
return True
|
|
2252
|
+
else:
|
|
2253
|
+
self._constraints.append(key)
|
|
2254
|
+
return False
|
|
2255
|
+
|
|
2256
|
+
def remove_constraint(self, int i):
|
|
2257
|
+
r"""
|
|
2258
|
+
Removes a constraint from ``self``.
|
|
2259
|
+
|
|
2260
|
+
INPUT:
|
|
2261
|
+
|
|
2262
|
+
- ``i`` -- index of the constraint to remove
|
|
2263
|
+
|
|
2264
|
+
EXAMPLES::
|
|
2265
|
+
|
|
2266
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2267
|
+
sage: x, y = p[0], p[1]
|
|
2268
|
+
sage: p.add_constraint(x + y, max=10)
|
|
2269
|
+
sage: p.add_constraint(x - y, max=0)
|
|
2270
|
+
sage: p.add_constraint(x, max=4)
|
|
2271
|
+
sage: p.show()
|
|
2272
|
+
Maximization:
|
|
2273
|
+
<BLANKLINE>
|
|
2274
|
+
Constraints:
|
|
2275
|
+
x_0 + x_1 <= 10.0
|
|
2276
|
+
x_0 - x_1 <= 0.0
|
|
2277
|
+
x_0 <= 4.0
|
|
2278
|
+
...
|
|
2279
|
+
sage: p.remove_constraint(1)
|
|
2280
|
+
sage: p.show()
|
|
2281
|
+
Maximization:
|
|
2282
|
+
<BLANKLINE>
|
|
2283
|
+
Constraints:
|
|
2284
|
+
x_0 + x_1 <= 10.0
|
|
2285
|
+
x_0 <= 4.0
|
|
2286
|
+
...
|
|
2287
|
+
sage: p.number_of_constraints()
|
|
2288
|
+
2
|
|
2289
|
+
"""
|
|
2290
|
+
if self._check_redundant:
|
|
2291
|
+
self._constraints.pop(i)
|
|
2292
|
+
self._backend.remove_constraint(i)
|
|
2293
|
+
|
|
2294
|
+
def remove_constraints(self, constraints):
|
|
2295
|
+
r"""
|
|
2296
|
+
Remove several constraints.
|
|
2297
|
+
|
|
2298
|
+
INPUT:
|
|
2299
|
+
|
|
2300
|
+
- ``constraints`` -- an iterable containing the indices of the rows to remove
|
|
2301
|
+
|
|
2302
|
+
EXAMPLES::
|
|
2303
|
+
|
|
2304
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2305
|
+
sage: x, y = p[0], p[1]
|
|
2306
|
+
sage: p.add_constraint(x + y, max=10)
|
|
2307
|
+
sage: p.add_constraint(x - y, max=0)
|
|
2308
|
+
sage: p.add_constraint(x, max=4)
|
|
2309
|
+
sage: p.show()
|
|
2310
|
+
Maximization:
|
|
2311
|
+
<BLANKLINE>
|
|
2312
|
+
Constraints:
|
|
2313
|
+
x_0 + x_1 <= 10.0
|
|
2314
|
+
x_0 - x_1 <= 0.0
|
|
2315
|
+
x_0 <= 4.0
|
|
2316
|
+
...
|
|
2317
|
+
sage: p.remove_constraints([0, 1])
|
|
2318
|
+
sage: p.show()
|
|
2319
|
+
Maximization:
|
|
2320
|
+
<BLANKLINE>
|
|
2321
|
+
Constraints:
|
|
2322
|
+
x_0 <= 4.0
|
|
2323
|
+
...
|
|
2324
|
+
sage: p.number_of_constraints()
|
|
2325
|
+
1
|
|
2326
|
+
|
|
2327
|
+
When checking for redundant constraints, make sure you remove only
|
|
2328
|
+
the constraints that were actually added. Problems could arise if
|
|
2329
|
+
you have a function that builds lps non-interactively, but it fails
|
|
2330
|
+
to check whether adding a constraint actually increases the number of
|
|
2331
|
+
constraints. The function might later try to remove constraints that
|
|
2332
|
+
are not actually there::
|
|
2333
|
+
|
|
2334
|
+
sage: p = MixedIntegerLinearProgram(check_redundant=True, solver='GLPK')
|
|
2335
|
+
sage: x, y = p[0], p[1]
|
|
2336
|
+
sage: p.add_constraint(x + y, max=10)
|
|
2337
|
+
sage: for each in range(10):
|
|
2338
|
+
....: p.add_constraint(x - y, max=10)
|
|
2339
|
+
sage: p.add_constraint(x, max=4)
|
|
2340
|
+
sage: p.number_of_constraints()
|
|
2341
|
+
3
|
|
2342
|
+
sage: p.remove_constraints(range(1, 9))
|
|
2343
|
+
Traceback (most recent call last):
|
|
2344
|
+
...
|
|
2345
|
+
IndexError: pop index out of range
|
|
2346
|
+
sage: p.remove_constraint(1)
|
|
2347
|
+
sage: p.number_of_constraints()
|
|
2348
|
+
2
|
|
2349
|
+
|
|
2350
|
+
We should now be able to add the old constraint back in::
|
|
2351
|
+
|
|
2352
|
+
sage: for each in range(10):
|
|
2353
|
+
....: p.add_constraint(x - y, max=10)
|
|
2354
|
+
sage: p.number_of_constraints()
|
|
2355
|
+
3
|
|
2356
|
+
|
|
2357
|
+
TESTS:
|
|
2358
|
+
|
|
2359
|
+
Removing no constraints does not make Sage crash, see
|
|
2360
|
+
:issue:`34881`::
|
|
2361
|
+
|
|
2362
|
+
sage: MixedIntegerLinearProgram().remove_constraints([])
|
|
2363
|
+
"""
|
|
2364
|
+
if self._check_redundant:
|
|
2365
|
+
for i in sorted(constraints, reverse=True):
|
|
2366
|
+
self._constraints.pop(i)
|
|
2367
|
+
if constraints:
|
|
2368
|
+
self._backend.remove_constraints(constraints)
|
|
2369
|
+
|
|
2370
|
+
def set_binary(self, ee):
|
|
2371
|
+
r"""
|
|
2372
|
+
Set a variable or a ``MIPVariable`` as binary.
|
|
2373
|
+
|
|
2374
|
+
INPUT:
|
|
2375
|
+
|
|
2376
|
+
- ``ee`` -- an instance of ``MIPVariable`` or one of
|
|
2377
|
+
its elements
|
|
2378
|
+
|
|
2379
|
+
EXAMPLES::
|
|
2380
|
+
|
|
2381
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2382
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2383
|
+
|
|
2384
|
+
With the following instruction, all the variables
|
|
2385
|
+
from x will be binary::
|
|
2386
|
+
|
|
2387
|
+
sage: p.set_binary(x)
|
|
2388
|
+
sage: p.set_objective(x[0] + x[1])
|
|
2389
|
+
sage: p.add_constraint(-3*x[0] + 2*x[1], max=2)
|
|
2390
|
+
|
|
2391
|
+
It is still possible, though, to set one of these
|
|
2392
|
+
variables as integer while keeping the others as they are::
|
|
2393
|
+
|
|
2394
|
+
sage: p.set_integer(x[3])
|
|
2395
|
+
"""
|
|
2396
|
+
cdef MIPVariable e
|
|
2397
|
+
e = <MIPVariable> ee
|
|
2398
|
+
|
|
2399
|
+
if isinstance(e, MIPVariable):
|
|
2400
|
+
e._vtype = self.__BINARY
|
|
2401
|
+
for v in e.values():
|
|
2402
|
+
self._backend.set_variable_type(self._variables[v],self.__BINARY)
|
|
2403
|
+
elif e in self._variables:
|
|
2404
|
+
self._backend.set_variable_type(self._variables[e],self.__BINARY)
|
|
2405
|
+
else:
|
|
2406
|
+
raise ValueError("e must be an instance of MIPVariable or one of its elements.")
|
|
2407
|
+
|
|
2408
|
+
def is_binary(self, e):
|
|
2409
|
+
r"""
|
|
2410
|
+
Test whether the variable ``e`` is binary. Variables are real by
|
|
2411
|
+
default.
|
|
2412
|
+
|
|
2413
|
+
INPUT:
|
|
2414
|
+
|
|
2415
|
+
- ``e`` -- a variable (not a ``MIPVariable``, but one of its elements)
|
|
2416
|
+
|
|
2417
|
+
OUTPUT: ``True`` if the variable ``e`` is binary; ``False`` otherwise
|
|
2418
|
+
|
|
2419
|
+
EXAMPLES::
|
|
2420
|
+
|
|
2421
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2422
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2423
|
+
sage: p.set_objective(v[1])
|
|
2424
|
+
sage: p.is_binary(v[1])
|
|
2425
|
+
False
|
|
2426
|
+
sage: p.set_binary(v[1])
|
|
2427
|
+
sage: p.is_binary(v[1])
|
|
2428
|
+
True
|
|
2429
|
+
"""
|
|
2430
|
+
return self._backend.is_variable_binary(self._variables[e])
|
|
2431
|
+
|
|
2432
|
+
def set_integer(self, ee):
|
|
2433
|
+
r"""
|
|
2434
|
+
Set a variable or a ``MIPVariable`` as integer.
|
|
2435
|
+
|
|
2436
|
+
INPUT:
|
|
2437
|
+
|
|
2438
|
+
- ``ee`` -- an instance of ``MIPVariable`` or one of
|
|
2439
|
+
its elements
|
|
2440
|
+
|
|
2441
|
+
EXAMPLES::
|
|
2442
|
+
|
|
2443
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2444
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2445
|
+
|
|
2446
|
+
With the following instruction, all the variables
|
|
2447
|
+
from x will be integers::
|
|
2448
|
+
|
|
2449
|
+
sage: p.set_integer(x)
|
|
2450
|
+
sage: p.set_objective(x[0] + x[1])
|
|
2451
|
+
sage: p.add_constraint(-3*x[0] + 2*x[1], max=2)
|
|
2452
|
+
|
|
2453
|
+
It is still possible, though, to set one of these
|
|
2454
|
+
variables as binary while keeping the others as they are::
|
|
2455
|
+
|
|
2456
|
+
sage: p.set_binary(x[3])
|
|
2457
|
+
"""
|
|
2458
|
+
cdef MIPVariable e
|
|
2459
|
+
e = <MIPVariable> ee
|
|
2460
|
+
|
|
2461
|
+
if isinstance(e, MIPVariable):
|
|
2462
|
+
e._vtype = self.__INTEGER
|
|
2463
|
+
for v in e.values():
|
|
2464
|
+
self._backend.set_variable_type(self._variables[v],self.__INTEGER)
|
|
2465
|
+
elif e in self._variables:
|
|
2466
|
+
self._backend.set_variable_type(self._variables[e],self.__INTEGER)
|
|
2467
|
+
else:
|
|
2468
|
+
raise ValueError("e must be an instance of MIPVariable or one of its elements.")
|
|
2469
|
+
|
|
2470
|
+
def is_integer(self, e):
|
|
2471
|
+
r"""
|
|
2472
|
+
Test whether the variable is an integer. Variables are real by
|
|
2473
|
+
default.
|
|
2474
|
+
|
|
2475
|
+
INPUT:
|
|
2476
|
+
|
|
2477
|
+
- ``e`` -- a variable (not a ``MIPVariable``, but one of its elements)
|
|
2478
|
+
|
|
2479
|
+
OUTPUT: ``True`` if the variable ``e`` is an integer; ``False`` otherwise
|
|
2480
|
+
|
|
2481
|
+
EXAMPLES::
|
|
2482
|
+
|
|
2483
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2484
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2485
|
+
sage: p.set_objective(v[1])
|
|
2486
|
+
sage: p.is_integer(v[1])
|
|
2487
|
+
False
|
|
2488
|
+
sage: p.set_integer(v[1])
|
|
2489
|
+
sage: p.is_integer(v[1])
|
|
2490
|
+
True
|
|
2491
|
+
"""
|
|
2492
|
+
return self._backend.is_variable_integer(self._variables[e])
|
|
2493
|
+
|
|
2494
|
+
def set_real(self, ee):
|
|
2495
|
+
r"""
|
|
2496
|
+
Set a variable or a ``MIPVariable`` as real.
|
|
2497
|
+
|
|
2498
|
+
INPUT:
|
|
2499
|
+
|
|
2500
|
+
- ``ee`` -- an instance of ``MIPVariable`` or one of
|
|
2501
|
+
its elements
|
|
2502
|
+
|
|
2503
|
+
EXAMPLES::
|
|
2504
|
+
|
|
2505
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2506
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2507
|
+
|
|
2508
|
+
With the following instruction, all the variables
|
|
2509
|
+
from x will be real::
|
|
2510
|
+
|
|
2511
|
+
sage: p.set_real(x)
|
|
2512
|
+
sage: p.set_objective(x[0] + x[1])
|
|
2513
|
+
sage: p.add_constraint(-3*x[0] + 2*x[1], max=2)
|
|
2514
|
+
|
|
2515
|
+
It is still possible, though, to set one of these
|
|
2516
|
+
variables as binary while keeping the others as they are::
|
|
2517
|
+
|
|
2518
|
+
sage: p.set_binary(x[3])
|
|
2519
|
+
"""
|
|
2520
|
+
cdef MIPVariable e
|
|
2521
|
+
e = <MIPVariable> ee
|
|
2522
|
+
|
|
2523
|
+
if isinstance(e, MIPVariable):
|
|
2524
|
+
e._vtype = self.__REAL
|
|
2525
|
+
for v in e.values():
|
|
2526
|
+
self._backend.set_variable_type(self._variables[v],self.__REAL)
|
|
2527
|
+
self._backend.variable_lower_bound(self._variables[v], 0)
|
|
2528
|
+
elif e in self._variables:
|
|
2529
|
+
self._backend.set_variable_type(self._variables[e],self.__REAL)
|
|
2530
|
+
self._backend.variable_lower_bound(self._variables[e], 0)
|
|
2531
|
+
else:
|
|
2532
|
+
raise ValueError("e must be an instance of MIPVariable or one of its elements.")
|
|
2533
|
+
|
|
2534
|
+
def is_real(self, e):
|
|
2535
|
+
r"""
|
|
2536
|
+
Test whether the variable is real.
|
|
2537
|
+
|
|
2538
|
+
INPUT:
|
|
2539
|
+
|
|
2540
|
+
- ``e`` -- a variable (not a ``MIPVariable``, but one of its elements)
|
|
2541
|
+
|
|
2542
|
+
OUTPUT: ``True`` if the variable is real; ``False`` otherwise
|
|
2543
|
+
|
|
2544
|
+
EXAMPLES::
|
|
2545
|
+
|
|
2546
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2547
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2548
|
+
sage: p.set_objective(v[1])
|
|
2549
|
+
sage: p.is_real(v[1])
|
|
2550
|
+
True
|
|
2551
|
+
sage: p.set_binary(v[1])
|
|
2552
|
+
sage: p.is_real(v[1])
|
|
2553
|
+
False
|
|
2554
|
+
sage: p.set_real(v[1])
|
|
2555
|
+
sage: p.is_real(v[1])
|
|
2556
|
+
True
|
|
2557
|
+
"""
|
|
2558
|
+
return self._backend.is_variable_continuous(self._variables[e])
|
|
2559
|
+
|
|
2560
|
+
def solve(self, log=None, objective_only=False):
|
|
2561
|
+
r"""
|
|
2562
|
+
Solve the ``MixedIntegerLinearProgram``.
|
|
2563
|
+
|
|
2564
|
+
INPUT:
|
|
2565
|
+
|
|
2566
|
+
- ``log`` -- integer (default: ``None``); the verbosity level. Indicates
|
|
2567
|
+
whether progress should be printed during computation. The solver is
|
|
2568
|
+
initialized to report no progress.
|
|
2569
|
+
|
|
2570
|
+
- ``objective_only`` -- boolean:
|
|
2571
|
+
|
|
2572
|
+
- When set to ``True``, only the objective function is returned.
|
|
2573
|
+
- When set to ``False`` (default), the optimal numerical values
|
|
2574
|
+
are stored (takes computational time).
|
|
2575
|
+
|
|
2576
|
+
OUTPUT: the optimal value taken by the objective function
|
|
2577
|
+
|
|
2578
|
+
.. WARNING::
|
|
2579
|
+
|
|
2580
|
+
By default, no additional assumption is made on the domain of an LP
|
|
2581
|
+
variable. See :meth:`set_min` and :meth:`set_max` to change it.
|
|
2582
|
+
|
|
2583
|
+
EXAMPLES:
|
|
2584
|
+
|
|
2585
|
+
Consider the following linear program::
|
|
2586
|
+
|
|
2587
|
+
Maximize:
|
|
2588
|
+
x + 5 * y
|
|
2589
|
+
Constraints:
|
|
2590
|
+
x + 0.2 y <= 4
|
|
2591
|
+
1.5 * x + 3 * y <= 4
|
|
2592
|
+
Variables:
|
|
2593
|
+
x is Real (min = 0, max = None)
|
|
2594
|
+
y is Real (min = 0, max = None)
|
|
2595
|
+
|
|
2596
|
+
This linear program can be solved as follows::
|
|
2597
|
+
|
|
2598
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
2599
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
2600
|
+
sage: p.set_objective(x[1] + 5*x[2])
|
|
2601
|
+
sage: p.add_constraint(x[1] + 0.2*x[2], max=4)
|
|
2602
|
+
sage: p.add_constraint(1.5*x[1] + 3*x[2], max=4)
|
|
2603
|
+
sage: round(p.solve(),6)
|
|
2604
|
+
6.666667
|
|
2605
|
+
sage: x = p.get_values(x)
|
|
2606
|
+
sage: round(x[1],6) # abs tol 1e-15
|
|
2607
|
+
0.0
|
|
2608
|
+
sage: round(x[2],6)
|
|
2609
|
+
1.333333
|
|
2610
|
+
|
|
2611
|
+
Computation of a maximum stable set in Petersen's graph::
|
|
2612
|
+
|
|
2613
|
+
sage: # needs sage.graphs
|
|
2614
|
+
sage: g = graphs.PetersenGraph()
|
|
2615
|
+
sage: p = MixedIntegerLinearProgram(maximization=True, solver='GLPK')
|
|
2616
|
+
sage: b = p.new_variable(nonnegative=True)
|
|
2617
|
+
sage: p.set_objective(sum([b[v] for v in g]))
|
|
2618
|
+
sage: for (u,v) in g.edges(sort=False, labels=None):
|
|
2619
|
+
....: p.add_constraint(b[u] + b[v], max=1)
|
|
2620
|
+
sage: p.set_binary(b)
|
|
2621
|
+
sage: p.solve(objective_only=True)
|
|
2622
|
+
4.0
|
|
2623
|
+
|
|
2624
|
+
Constraints in the objective function are respected::
|
|
2625
|
+
|
|
2626
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2627
|
+
sage: x, y = p[0], p[1]
|
|
2628
|
+
sage: p.add_constraint(2*x + 3*y, max=6)
|
|
2629
|
+
sage: p.add_constraint(3*x + 2*y, max=6)
|
|
2630
|
+
sage: p.set_objective(x + y + 7)
|
|
2631
|
+
sage: p.set_integer(x); p.set_integer(y)
|
|
2632
|
+
sage: p.solve()
|
|
2633
|
+
9.0
|
|
2634
|
+
"""
|
|
2635
|
+
if log is not None:
|
|
2636
|
+
self._backend.set_verbosity(log)
|
|
2637
|
+
self._backend.solve()
|
|
2638
|
+
return self._backend.get_objective_value()
|
|
2639
|
+
|
|
2640
|
+
def set_min(self, v, min):
|
|
2641
|
+
r"""
|
|
2642
|
+
Set the minimum value of a variable.
|
|
2643
|
+
|
|
2644
|
+
INPUT:
|
|
2645
|
+
|
|
2646
|
+
- ``v`` -- a variable
|
|
2647
|
+
|
|
2648
|
+
- ``min`` -- the minimum value the variable can take; when
|
|
2649
|
+
``min=None``, the variable has no lower bound
|
|
2650
|
+
|
|
2651
|
+
.. SEEALSO::
|
|
2652
|
+
|
|
2653
|
+
- :meth:`get_min` -- get the minimum value of a variable
|
|
2654
|
+
|
|
2655
|
+
EXAMPLES::
|
|
2656
|
+
|
|
2657
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2658
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2659
|
+
sage: p.set_objective(v[1])
|
|
2660
|
+
sage: p.get_min(v[1])
|
|
2661
|
+
0.0
|
|
2662
|
+
sage: p.set_min(v[1],6)
|
|
2663
|
+
sage: p.get_min(v[1])
|
|
2664
|
+
6.0
|
|
2665
|
+
sage: p.set_min(v[1], None)
|
|
2666
|
+
sage: p.get_min(v[1])
|
|
2667
|
+
|
|
2668
|
+
With a :class:`MIPVariable` as an argument::
|
|
2669
|
+
|
|
2670
|
+
sage: vv = p.new_variable(real=True)
|
|
2671
|
+
sage: p.get_min(vv)
|
|
2672
|
+
sage: p.get_min(vv[0])
|
|
2673
|
+
sage: p.set_min(vv,5)
|
|
2674
|
+
sage: p.get_min(vv[0])
|
|
2675
|
+
5.0
|
|
2676
|
+
sage: p.get_min(vv[9])
|
|
2677
|
+
5.0
|
|
2678
|
+
"""
|
|
2679
|
+
try:
|
|
2680
|
+
v.set_min(min)
|
|
2681
|
+
except AttributeError:
|
|
2682
|
+
self._backend.variable_lower_bound(self._variables[v], min)
|
|
2683
|
+
|
|
2684
|
+
def set_max(self, v, max):
|
|
2685
|
+
r"""
|
|
2686
|
+
Set the maximum value of a variable.
|
|
2687
|
+
|
|
2688
|
+
INPUT:
|
|
2689
|
+
|
|
2690
|
+
- ``v`` -- a variable
|
|
2691
|
+
|
|
2692
|
+
- ``max`` -- the maximum value the variable can take; when
|
|
2693
|
+
``max=None``, the variable has no upper bound
|
|
2694
|
+
|
|
2695
|
+
EXAMPLES::
|
|
2696
|
+
|
|
2697
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2698
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2699
|
+
sage: p.set_objective(v[1])
|
|
2700
|
+
sage: p.get_max(v[1])
|
|
2701
|
+
sage: p.set_max(v[1],6)
|
|
2702
|
+
sage: p.get_max(v[1])
|
|
2703
|
+
6.0
|
|
2704
|
+
|
|
2705
|
+
With a :class:`MIPVariable` as an argument::
|
|
2706
|
+
|
|
2707
|
+
sage: vv = p.new_variable(real=True)
|
|
2708
|
+
sage: p.get_max(vv)
|
|
2709
|
+
sage: p.get_max(vv[0])
|
|
2710
|
+
sage: p.set_max(vv,5)
|
|
2711
|
+
sage: p.get_max(vv[0])
|
|
2712
|
+
5.0
|
|
2713
|
+
sage: p.get_max(vv[9])
|
|
2714
|
+
5.0
|
|
2715
|
+
"""
|
|
2716
|
+
try:
|
|
2717
|
+
v.set_max(max)
|
|
2718
|
+
except AttributeError:
|
|
2719
|
+
self._backend.variable_upper_bound(self._variables[v], max)
|
|
2720
|
+
|
|
2721
|
+
def get_min(self, v):
|
|
2722
|
+
r"""
|
|
2723
|
+
Return the minimum value of a variable.
|
|
2724
|
+
|
|
2725
|
+
INPUT:
|
|
2726
|
+
|
|
2727
|
+
- ``v`` -- a variable
|
|
2728
|
+
|
|
2729
|
+
OUTPUT:
|
|
2730
|
+
|
|
2731
|
+
Minimum value of the variable, or ``None`` if the variable has no lower
|
|
2732
|
+
bound.
|
|
2733
|
+
|
|
2734
|
+
EXAMPLES::
|
|
2735
|
+
|
|
2736
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2737
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2738
|
+
sage: p.set_objective(v[1])
|
|
2739
|
+
sage: p.get_min(v[1])
|
|
2740
|
+
0.0
|
|
2741
|
+
sage: p.set_min(v[1],6)
|
|
2742
|
+
sage: p.get_min(v[1])
|
|
2743
|
+
6.0
|
|
2744
|
+
sage: p.set_min(v[1], None)
|
|
2745
|
+
sage: p.get_min(v[1])
|
|
2746
|
+
"""
|
|
2747
|
+
try:
|
|
2748
|
+
return (<MIPVariable?>v)._lower_bound
|
|
2749
|
+
except TypeError:
|
|
2750
|
+
return self._backend.variable_lower_bound(self._variables[v])
|
|
2751
|
+
|
|
2752
|
+
def get_max(self, v):
|
|
2753
|
+
r"""
|
|
2754
|
+
Return the maximum value of a variable.
|
|
2755
|
+
|
|
2756
|
+
INPUT:
|
|
2757
|
+
|
|
2758
|
+
- ``v`` -- a variable
|
|
2759
|
+
|
|
2760
|
+
OUTPUT:
|
|
2761
|
+
|
|
2762
|
+
Maximum value of the variable, or ``None`` if the variable has no upper
|
|
2763
|
+
bound.
|
|
2764
|
+
|
|
2765
|
+
EXAMPLES::
|
|
2766
|
+
|
|
2767
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2768
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2769
|
+
sage: p.set_objective(v[1])
|
|
2770
|
+
sage: p.get_max(v[1])
|
|
2771
|
+
sage: p.set_max(v[1],6)
|
|
2772
|
+
sage: p.get_max(v[1])
|
|
2773
|
+
6.0
|
|
2774
|
+
"""
|
|
2775
|
+
try:
|
|
2776
|
+
return (<MIPVariable?>v)._upper_bound
|
|
2777
|
+
except TypeError:
|
|
2778
|
+
return self._backend.variable_upper_bound(self._variables[v])
|
|
2779
|
+
|
|
2780
|
+
def solver_parameter(self, name, value=None):
|
|
2781
|
+
"""
|
|
2782
|
+
Return or define a solver parameter.
|
|
2783
|
+
|
|
2784
|
+
The solver parameters are by essence solver-specific, which means their
|
|
2785
|
+
meaning heavily depends on the solver used.
|
|
2786
|
+
|
|
2787
|
+
(If you do not know which solver you are using, then you use GLPK).
|
|
2788
|
+
|
|
2789
|
+
Aliases:
|
|
2790
|
+
|
|
2791
|
+
Very common parameters have aliases making them solver-independent. For
|
|
2792
|
+
example, the following::
|
|
2793
|
+
|
|
2794
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2795
|
+
sage: p.solver_parameter("timelimit", 60)
|
|
2796
|
+
|
|
2797
|
+
Sets the solver to stop its computations after 60 seconds, and works
|
|
2798
|
+
with GLPK, CPLEX , SCIP, and Gurobi.
|
|
2799
|
+
|
|
2800
|
+
- ``'timelimit'`` -- defines the maximum time spent on a
|
|
2801
|
+
computation (measured in seconds)
|
|
2802
|
+
|
|
2803
|
+
Another example is the ``'logfile'`` parameter, which is used to specify
|
|
2804
|
+
the file in which computation logs are recorded. By default, the logs
|
|
2805
|
+
are not recorded, and we can disable this feature providing an empty
|
|
2806
|
+
filename. This is currently working with CPLEX and Gurobi::
|
|
2807
|
+
|
|
2808
|
+
sage: # optional - cplex
|
|
2809
|
+
sage: p = MixedIntegerLinearProgram(solver='CPLEX')
|
|
2810
|
+
sage: p.solver_parameter("logfile")
|
|
2811
|
+
''
|
|
2812
|
+
sage: p.solver_parameter("logfile", "/dev/null")
|
|
2813
|
+
sage: p.solver_parameter("logfile")
|
|
2814
|
+
'/dev/null'
|
|
2815
|
+
sage: p.solver_parameter("logfile", '')
|
|
2816
|
+
sage: p.solver_parameter("logfile")
|
|
2817
|
+
''
|
|
2818
|
+
|
|
2819
|
+
Solver-specific parameters:
|
|
2820
|
+
|
|
2821
|
+
- GLPK : We have implemented very close to comprehensive coverage of
|
|
2822
|
+
the GLPK solver parameters for the simplex and integer
|
|
2823
|
+
optimization methods. For details, see the documentation of
|
|
2824
|
+
:meth:`GLPKBackend.solver_parameter
|
|
2825
|
+
<sage.numerical.backends.glpk_backend.GLPKBackend.solver_parameter>`.
|
|
2826
|
+
|
|
2827
|
+
- CPLEX's parameters are identified by a string. Their
|
|
2828
|
+
list is available `on ILOG's website
|
|
2829
|
+
<http://publib.boulder.ibm.com/infocenter/odmeinfo/v3r4/index.jsp?topic=/ilog.odms.ide.odme.help/Content/Optimization/Documentation/ODME/_pubskel/ODME_pubskels/startall_ODME34_Eclipse1590.html>`_.
|
|
2830
|
+
|
|
2831
|
+
The command ::
|
|
2832
|
+
|
|
2833
|
+
sage: p = MixedIntegerLinearProgram(solver='CPLEX') # optional - CPLEX
|
|
2834
|
+
sage: p.solver_parameter("CPX_PARAM_TILIM", 60) # optional - CPLEX
|
|
2835
|
+
|
|
2836
|
+
works as intended.
|
|
2837
|
+
|
|
2838
|
+
- Gurobi's parameters should all be available through this
|
|
2839
|
+
method. Their list is available on Gurobi's website
|
|
2840
|
+
`<http://www.gurobi.com/documentation/5.5/reference-manual/node798>`_.
|
|
2841
|
+
|
|
2842
|
+
SCIP's parameter can be found here:
|
|
2843
|
+
`<http://scip.zib.de/doc-5.0.1/html/PARAMETERS.php>`_.
|
|
2844
|
+
|
|
2845
|
+
INPUT:
|
|
2846
|
+
|
|
2847
|
+
- ``name`` -- string; the parameter
|
|
2848
|
+
|
|
2849
|
+
- ``value`` -- the parameter's value if it is to be defined,
|
|
2850
|
+
or ``None`` (default) to obtain its current value
|
|
2851
|
+
|
|
2852
|
+
EXAMPLES::
|
|
2853
|
+
|
|
2854
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2855
|
+
sage: p.solver_parameter("timelimit", 60)
|
|
2856
|
+
sage: p.solver_parameter("timelimit")
|
|
2857
|
+
60.0
|
|
2858
|
+
"""
|
|
2859
|
+
if value is None:
|
|
2860
|
+
return self._backend.solver_parameter(name)
|
|
2861
|
+
else:
|
|
2862
|
+
self._backend.solver_parameter(name, value)
|
|
2863
|
+
|
|
2864
|
+
cpdef sum(self, L):
|
|
2865
|
+
r"""
|
|
2866
|
+
Efficiently compute the sum of a sequence of
|
|
2867
|
+
:class:`~sage.numerical.linear_functions.LinearFunction` elements
|
|
2868
|
+
|
|
2869
|
+
INPUT:
|
|
2870
|
+
|
|
2871
|
+
- ``mip`` -- the :class:`MixedIntegerLinearProgram` parent
|
|
2872
|
+
|
|
2873
|
+
- ``L`` -- list of
|
|
2874
|
+
:class:`~sage.numerical.linear_functions.LinearFunction` instances
|
|
2875
|
+
|
|
2876
|
+
.. NOTE::
|
|
2877
|
+
|
|
2878
|
+
The use of the regular ``sum`` function is not recommended
|
|
2879
|
+
as it is much less efficient than this one
|
|
2880
|
+
|
|
2881
|
+
EXAMPLES::
|
|
2882
|
+
|
|
2883
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2884
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
2885
|
+
|
|
2886
|
+
The following command::
|
|
2887
|
+
|
|
2888
|
+
sage: s = p.sum(v[i] for i in range(90))
|
|
2889
|
+
|
|
2890
|
+
is much more efficient than::
|
|
2891
|
+
|
|
2892
|
+
sage: s = sum(v[i] for i in range(90))
|
|
2893
|
+
"""
|
|
2894
|
+
d = {}
|
|
2895
|
+
for v in L:
|
|
2896
|
+
for id, coeff in v.iteritems():
|
|
2897
|
+
d[id] = coeff + d.get(id, 0)
|
|
2898
|
+
return self.linear_functions_parent()(d)
|
|
2899
|
+
|
|
2900
|
+
def get_backend(self):
|
|
2901
|
+
r"""
|
|
2902
|
+
Return the backend instance used.
|
|
2903
|
+
|
|
2904
|
+
This might be useful when access to additional functions provided by
|
|
2905
|
+
the backend is needed.
|
|
2906
|
+
|
|
2907
|
+
EXAMPLES:
|
|
2908
|
+
|
|
2909
|
+
This example uses the simplex algorithm and prints information::
|
|
2910
|
+
|
|
2911
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2912
|
+
sage: x, y = p[0], p[1]
|
|
2913
|
+
sage: p.add_constraint(2*x + 3*y, max=6)
|
|
2914
|
+
sage: p.add_constraint(3*x + 2*y, max=6)
|
|
2915
|
+
sage: p.set_objective(x + y + 7)
|
|
2916
|
+
sage: b = p.get_backend()
|
|
2917
|
+
sage: b.solver_parameter("simplex_or_intopt", "simplex_only")
|
|
2918
|
+
sage: b.solver_parameter("verbosity_simplex", "GLP_MSG_ALL")
|
|
2919
|
+
sage: ans = p.solve()
|
|
2920
|
+
GLPK Simplex Optimizer...
|
|
2921
|
+
2 rows, 2 columns, 4 non-zeros
|
|
2922
|
+
* 0: obj = 7.000000000e+00 inf = 0.000e+00 (2)
|
|
2923
|
+
* 2: obj = 9.400000000e+00 inf = 0.000e+00 (0)
|
|
2924
|
+
OPTIMAL LP SOLUTION FOUND
|
|
2925
|
+
sage: ans # rel tol 1e-5
|
|
2926
|
+
9.4
|
|
2927
|
+
"""
|
|
2928
|
+
return self._backend
|
|
2929
|
+
|
|
2930
|
+
def get_objective_value(self):
|
|
2931
|
+
"""
|
|
2932
|
+
Return the value of the objective function.
|
|
2933
|
+
|
|
2934
|
+
.. NOTE::
|
|
2935
|
+
|
|
2936
|
+
Behaviour is undefined unless ``solve`` has been called before.
|
|
2937
|
+
|
|
2938
|
+
EXAMPLES::
|
|
2939
|
+
|
|
2940
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2941
|
+
sage: x, y = p[0], p[1]
|
|
2942
|
+
sage: p.add_constraint(2*x + 3*y, max=6)
|
|
2943
|
+
sage: p.add_constraint(3*x + 2*y, max=6)
|
|
2944
|
+
sage: p.set_objective(x + y + 7)
|
|
2945
|
+
sage: p.solve() # rel tol 1e-5
|
|
2946
|
+
9.4
|
|
2947
|
+
sage: p.get_objective_value() # rel tol 1e-5
|
|
2948
|
+
9.4
|
|
2949
|
+
"""
|
|
2950
|
+
return self._backend.get_objective_value()
|
|
2951
|
+
|
|
2952
|
+
def best_known_objective_bound(self):
|
|
2953
|
+
r"""
|
|
2954
|
+
Return the value of the currently best known bound.
|
|
2955
|
+
|
|
2956
|
+
This method returns the current best upper (resp. lower) bound
|
|
2957
|
+
on the optimal value of the objective function in a
|
|
2958
|
+
maximization (resp. minimization) problem. It is equal to the
|
|
2959
|
+
output of :meth:`get_objective_value` if the MILP found an
|
|
2960
|
+
optimal solution, but it can differ if it was interrupted
|
|
2961
|
+
manually or after a time limit (cf :meth:`solver_parameter`).
|
|
2962
|
+
|
|
2963
|
+
.. NOTE::
|
|
2964
|
+
|
|
2965
|
+
Has no meaning unless ``solve`` has been called before.
|
|
2966
|
+
|
|
2967
|
+
EXAMPLES::
|
|
2968
|
+
|
|
2969
|
+
sage: # needs sage.graphs
|
|
2970
|
+
sage: g = graphs.CubeGraph(9)
|
|
2971
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
2972
|
+
sage: p.solver_parameter("mip_gap_tolerance",100)
|
|
2973
|
+
sage: b = p.new_variable(binary=True)
|
|
2974
|
+
sage: p.set_objective(p.sum(b[v] for v in g))
|
|
2975
|
+
sage: for v in g:
|
|
2976
|
+
....: p.add_constraint(b[v] + p.sum(b[u] for u in g.neighbors(v)) <= 1)
|
|
2977
|
+
sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution
|
|
2978
|
+
sage: p.solve() # rel tol 100
|
|
2979
|
+
1.0
|
|
2980
|
+
sage: p.best_known_objective_bound() # random
|
|
2981
|
+
48.0
|
|
2982
|
+
"""
|
|
2983
|
+
return self._backend.best_known_objective_bound()
|
|
2984
|
+
|
|
2985
|
+
def get_relative_objective_gap(self):
|
|
2986
|
+
r"""
|
|
2987
|
+
Return the relative objective gap of the best known solution.
|
|
2988
|
+
|
|
2989
|
+
For a minimization problem, this value is computed by
|
|
2990
|
+
`(\texttt{bestinteger} - \texttt{bestobjective}) / (1e-10 +
|
|
2991
|
+
|\texttt{bestobjective}|)`, where ``bestinteger`` is the value returned
|
|
2992
|
+
by :meth:`~MixedIntegerLinearProgram.get_objective_value` and
|
|
2993
|
+
``bestobjective`` is the value returned by
|
|
2994
|
+
:meth:`~MixedIntegerLinearProgram.best_known_objective_bound`. For a
|
|
2995
|
+
maximization problem, the value is computed by `(\texttt{bestobjective}
|
|
2996
|
+
- \texttt{bestinteger}) / (1e-10 + |\texttt{bestobjective}|)`.
|
|
2997
|
+
|
|
2998
|
+
.. NOTE::
|
|
2999
|
+
|
|
3000
|
+
Has no meaning unless ``solve`` has been called before.
|
|
3001
|
+
|
|
3002
|
+
EXAMPLES::
|
|
3003
|
+
|
|
3004
|
+
sage: # needs sage.graphs
|
|
3005
|
+
sage: g = graphs.CubeGraph(9)
|
|
3006
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3007
|
+
sage: p.solver_parameter("mip_gap_tolerance",100)
|
|
3008
|
+
sage: b = p.new_variable(binary=True)
|
|
3009
|
+
sage: p.set_objective(p.sum(b[v] for v in g))
|
|
3010
|
+
sage: for v in g:
|
|
3011
|
+
....: p.add_constraint(b[v] + p.sum(b[u] for u in g.neighbors(v)) <= 1)
|
|
3012
|
+
sage: p.add_constraint(b[v] == 1) # Force an easy non-0 solution
|
|
3013
|
+
sage: p.solve() # rel tol 100
|
|
3014
|
+
1.0
|
|
3015
|
+
sage: p.get_relative_objective_gap() # random
|
|
3016
|
+
46.99999999999999
|
|
3017
|
+
|
|
3018
|
+
TESTS:
|
|
3019
|
+
|
|
3020
|
+
Just make sure that the variable *has* been defined, and is not just
|
|
3021
|
+
undefined::
|
|
3022
|
+
|
|
3023
|
+
sage: p.get_relative_objective_gap() > 1 # needs sage.graphs
|
|
3024
|
+
True
|
|
3025
|
+
"""
|
|
3026
|
+
return self._backend.get_relative_objective_gap()
|
|
3027
|
+
|
|
3028
|
+
def interactive_lp_problem(self, form='standard'):
|
|
3029
|
+
r"""
|
|
3030
|
+
Return an InteractiveLPProblem and, if available, a basis.
|
|
3031
|
+
|
|
3032
|
+
INPUT:
|
|
3033
|
+
|
|
3034
|
+
- ``form`` -- (default: ``'standard'``) a string specifying return type: either
|
|
3035
|
+
``None``, or ``'std'`` or ``'standard'``, respectively returns an instance of
|
|
3036
|
+
:class:`InteractiveLPProblem` or of :class:`InteractiveLPProblemStandardForm`
|
|
3037
|
+
|
|
3038
|
+
OUTPUT:
|
|
3039
|
+
|
|
3040
|
+
A 2-tuple consists of an instance of class :class:`InteractiveLPProblem` or
|
|
3041
|
+
:class:`InteractiveLPProblemStandardForm` that is constructed based on a given
|
|
3042
|
+
:class:`MixedIntegerLinearProgram`, and a list of basic
|
|
3043
|
+
variables (the basis) if standard form is chosen (by default), otherwise ``None``.
|
|
3044
|
+
|
|
3045
|
+
All variables must have 0 as lower bound and no upper bound.
|
|
3046
|
+
|
|
3047
|
+
EXAMPLES::
|
|
3048
|
+
|
|
3049
|
+
sage: p = MixedIntegerLinearProgram(names=['m'], solver='GLPK')
|
|
3050
|
+
sage: x = p.new_variable(nonnegative=True)
|
|
3051
|
+
sage: y = p.new_variable(nonnegative=True, name='n')
|
|
3052
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3053
|
+
sage: p.add_constraint(x[0] + x[1] - 7*y[0] + v[0] <= 2, name='K')
|
|
3054
|
+
sage: p.add_constraint(x[1] + 2*y[0] - v[0] <= 3)
|
|
3055
|
+
sage: p.add_constraint(5*x[0] + y[0] <= 21, name='L')
|
|
3056
|
+
sage: p.set_objective(2*x[0] + 3*x[1] + 4*y[0] + 5*v[0])
|
|
3057
|
+
sage: lp, basis = p.interactive_lp_problem()
|
|
3058
|
+
sage: basis
|
|
3059
|
+
['K', 'w_1', 'L']
|
|
3060
|
+
sage: lp.constraint_coefficients()
|
|
3061
|
+
[ 1.0 1.0 -7.0 1.0]
|
|
3062
|
+
[ 0.0 1.0 2.0 -1.0]
|
|
3063
|
+
[ 5.0 0.0 1.0 0.0]
|
|
3064
|
+
sage: lp.b()
|
|
3065
|
+
(2.0, 3.0, 21.0)
|
|
3066
|
+
sage: lp.objective_coefficients()
|
|
3067
|
+
(2.0, 3.0, 4.0, 5.0)
|
|
3068
|
+
sage: lp.decision_variables()
|
|
3069
|
+
(m_0, m_1, n_0, x_3)
|
|
3070
|
+
sage: view(lp) #not tested
|
|
3071
|
+
sage: d = lp.dictionary(*basis)
|
|
3072
|
+
sage: view(d) #not tested
|
|
3073
|
+
|
|
3074
|
+
TESTS::
|
|
3075
|
+
|
|
3076
|
+
sage: b = p.get_backend()
|
|
3077
|
+
sage: import sage.numerical.backends.glpk_backend as backend
|
|
3078
|
+
sage: b.solver_parameter(backend.glp_simplex_or_intopt, backend.glp_simplex_only)
|
|
3079
|
+
sage: b.solve()
|
|
3080
|
+
0
|
|
3081
|
+
sage: lp2, basis2 = p.interactive_lp_problem()
|
|
3082
|
+
sage: set(basis2)
|
|
3083
|
+
{'n_0', 'w_1', 'x_3'}
|
|
3084
|
+
sage: d2 = lp2.dictionary(*basis2)
|
|
3085
|
+
sage: d2.is_optimal()
|
|
3086
|
+
True
|
|
3087
|
+
sage: view(d2) #not tested
|
|
3088
|
+
|
|
3089
|
+
sage: lp3, _ = p.interactive_lp_problem(form=None)
|
|
3090
|
+
sage: lp3.constraint_coefficients()
|
|
3091
|
+
[ 1.0 1.0 -7.0 1.0]
|
|
3092
|
+
[ 0.0 1.0 2.0 -1.0]
|
|
3093
|
+
[ 5.0 0.0 1.0 0.0]
|
|
3094
|
+
sage: lp3.b()
|
|
3095
|
+
(2.0, 3.0, 21.0)
|
|
3096
|
+
sage: lp3.objective_coefficients()
|
|
3097
|
+
(2.0, 3.0, 4.0, 5.0)
|
|
3098
|
+
sage: lp3.decision_variables()
|
|
3099
|
+
(m_0, m_1, n_0, x_3)
|
|
3100
|
+
sage: view(lp3) #not tested
|
|
3101
|
+
"""
|
|
3102
|
+
back_end = self.get_backend()
|
|
3103
|
+
for i in range(self.number_of_variables()):
|
|
3104
|
+
if back_end.variable_lower_bound(i) != 0:
|
|
3105
|
+
raise ValueError('Problem variables must have 0 as lower bound')
|
|
3106
|
+
if back_end.variable_upper_bound(i) is not None:
|
|
3107
|
+
raise ValueError('Problem variables must not have upper bound')
|
|
3108
|
+
|
|
3109
|
+
# Construct 'A'
|
|
3110
|
+
coef_matrix = []
|
|
3111
|
+
for constraint in self.constraints():
|
|
3112
|
+
coef_row = [0] * self.number_of_variables()
|
|
3113
|
+
for index, value in zip(constraint[1][0],constraint[1][1]):
|
|
3114
|
+
coef_row[index] = value
|
|
3115
|
+
coef_matrix.append(coef_row)
|
|
3116
|
+
|
|
3117
|
+
# Construct 'b'
|
|
3118
|
+
upper_bound_vector = [c[2] for c in self.constraints()]
|
|
3119
|
+
|
|
3120
|
+
# Raise exception if exist lower bound
|
|
3121
|
+
for constraint in self.constraints():
|
|
3122
|
+
if constraint[0] is not None:
|
|
3123
|
+
raise ValueError('Problem constraints cannot have lower bounds')
|
|
3124
|
+
|
|
3125
|
+
# Construct 'c'
|
|
3126
|
+
def get_obj_coef(i):
|
|
3127
|
+
return back_end.objective_coefficient(i)
|
|
3128
|
+
objective_coefs_vector = [get_obj_coef(i) for i in range(self.number_of_variables())]
|
|
3129
|
+
|
|
3130
|
+
def format(name, prefix, index):
|
|
3131
|
+
if name:
|
|
3132
|
+
return name.replace('[','_').strip(']')
|
|
3133
|
+
else:
|
|
3134
|
+
return prefix + '_' + str(index)
|
|
3135
|
+
|
|
3136
|
+
# Construct 'x'
|
|
3137
|
+
var_names = [format(back_end.col_name(i), 'x', i) for i in range(back_end.ncols())]
|
|
3138
|
+
|
|
3139
|
+
A = coef_matrix
|
|
3140
|
+
b = upper_bound_vector
|
|
3141
|
+
c = objective_coefs_vector
|
|
3142
|
+
x = var_names
|
|
3143
|
+
|
|
3144
|
+
if form is None:
|
|
3145
|
+
from sage.numerical.interactive_simplex_method import InteractiveLPProblem
|
|
3146
|
+
return InteractiveLPProblem(A, b, c, x), None
|
|
3147
|
+
elif form == 'standard' or form == 'std':
|
|
3148
|
+
# Construct slack names
|
|
3149
|
+
slack_names = [format(back_end.row_name(i), 'w', i) for i in range(back_end.nrows())]
|
|
3150
|
+
w = slack_names
|
|
3151
|
+
from sage.numerical.interactive_simplex_method import InteractiveLPProblemStandardForm
|
|
3152
|
+
lp = InteractiveLPProblemStandardForm(A, b, c, x, slack_variables=w)
|
|
3153
|
+
basic_variables = []
|
|
3154
|
+
for i, e in enumerate(lp.x()):
|
|
3155
|
+
if back_end.is_variable_basic(i):
|
|
3156
|
+
basic_variables.append(str(e))
|
|
3157
|
+
elif not back_end.is_variable_nonbasic_at_lower_bound(i):
|
|
3158
|
+
raise ValueError('Invalid column status')
|
|
3159
|
+
for i, e in enumerate(lp.slack_variables()):
|
|
3160
|
+
if back_end.is_slack_variable_basic(i):
|
|
3161
|
+
basic_variables.append(str(e))
|
|
3162
|
+
elif not back_end.is_slack_variable_nonbasic_at_lower_bound(i):
|
|
3163
|
+
raise ValueError('Invalid row status')
|
|
3164
|
+
return lp, basic_variables
|
|
3165
|
+
else:
|
|
3166
|
+
raise ValueError('Form of interactive_lp_problem must be either None or \'standard\'')
|
|
3167
|
+
|
|
3168
|
+
|
|
3169
|
+
class MIPSolverException(RuntimeError):
|
|
3170
|
+
r"""
|
|
3171
|
+
Exception raised when the solver fails.
|
|
3172
|
+
|
|
3173
|
+
EXAMPLES::
|
|
3174
|
+
|
|
3175
|
+
sage: from sage.numerical.mip import MIPSolverException
|
|
3176
|
+
sage: e = MIPSolverException("Error")
|
|
3177
|
+
sage: e
|
|
3178
|
+
MIPSolverException('Error'...)
|
|
3179
|
+
sage: print(e)
|
|
3180
|
+
Error
|
|
3181
|
+
|
|
3182
|
+
TESTS:
|
|
3183
|
+
|
|
3184
|
+
No continuous solution::
|
|
3185
|
+
|
|
3186
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3187
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3188
|
+
sage: p.add_constraint(v[0], max=5.5)
|
|
3189
|
+
sage: p.add_constraint(v[0], min=7.6)
|
|
3190
|
+
sage: p.set_objective(v[0])
|
|
3191
|
+
|
|
3192
|
+
Tests of GLPK's Exceptions::
|
|
3193
|
+
|
|
3194
|
+
sage: p.solve()
|
|
3195
|
+
Traceback (most recent call last):
|
|
3196
|
+
...
|
|
3197
|
+
MIPSolverException: GLPK: Problem has no feasible solution
|
|
3198
|
+
|
|
3199
|
+
No integer solution::
|
|
3200
|
+
|
|
3201
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3202
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3203
|
+
sage: p.add_constraint(v[0], max=5.6)
|
|
3204
|
+
sage: p.add_constraint(v[0], min=5.2)
|
|
3205
|
+
sage: p.set_objective(v[0])
|
|
3206
|
+
sage: p.set_integer(v)
|
|
3207
|
+
|
|
3208
|
+
Tests of GLPK's Exceptions::
|
|
3209
|
+
|
|
3210
|
+
sage: p.solve()
|
|
3211
|
+
Traceback (most recent call last):
|
|
3212
|
+
...
|
|
3213
|
+
MIPSolverException: GLPK: Problem has no feasible solution
|
|
3214
|
+
"""
|
|
3215
|
+
pass
|
|
3216
|
+
|
|
3217
|
+
|
|
3218
|
+
cdef class MIPVariable(FiniteFamily):
|
|
3219
|
+
r"""
|
|
3220
|
+
``MIPVariable`` is a variable used by the class
|
|
3221
|
+
``MixedIntegerLinearProgram``.
|
|
3222
|
+
|
|
3223
|
+
.. WARNING::
|
|
3224
|
+
|
|
3225
|
+
You should not instantiate this class directly. Instead, use
|
|
3226
|
+
:meth:`MixedIntegerLinearProgram.new_variable`.
|
|
3227
|
+
"""
|
|
3228
|
+
def __init__(self, mip, vtype, name='', lower_bound=0, upper_bound=None,
|
|
3229
|
+
indices=None):
|
|
3230
|
+
r"""
|
|
3231
|
+
Constructor for ``MIPVariable``.
|
|
3232
|
+
|
|
3233
|
+
INPUT:
|
|
3234
|
+
|
|
3235
|
+
- ``parent`` -- :class:`MIPVariableParent`; the parent of the
|
|
3236
|
+
MIP variable
|
|
3237
|
+
|
|
3238
|
+
- ``mip`` -- :class:`MixedIntegerLinearProgram`; the
|
|
3239
|
+
underlying linear program
|
|
3240
|
+
|
|
3241
|
+
- ``vtype`` -- integer; defines the type of the variables
|
|
3242
|
+
(default: ``REAL``, i.e., ``vtype=-1``)
|
|
3243
|
+
|
|
3244
|
+
- ``name`` -- a name for the ``MIPVariable``
|
|
3245
|
+
|
|
3246
|
+
- ``lower_bound``, ``upper_bound`` -- lower bound and upper
|
|
3247
|
+
bound on the variable. Set to ``None`` to indicate that the
|
|
3248
|
+
variable is unbounded.
|
|
3249
|
+
|
|
3250
|
+
- ``indices`` -- (optional) an iterable of keys; components
|
|
3251
|
+
corresponding to these keys are created in order,
|
|
3252
|
+
and access to components with other keys will raise an
|
|
3253
|
+
error; otherwise components of this variable can be
|
|
3254
|
+
indexed by arbitrary keys and are created dynamically
|
|
3255
|
+
on access
|
|
3256
|
+
|
|
3257
|
+
For more information, see the method
|
|
3258
|
+
``MixedIntegerLinearProgram.new_variable``.
|
|
3259
|
+
|
|
3260
|
+
EXAMPLES::
|
|
3261
|
+
|
|
3262
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3263
|
+
sage: p.new_variable(nonnegative=True)
|
|
3264
|
+
MIPVariable with 0 real components, >= 0
|
|
3265
|
+
"""
|
|
3266
|
+
super().__init__({})
|
|
3267
|
+
self._p = mip
|
|
3268
|
+
self._vtype = vtype
|
|
3269
|
+
self._lower_bound = lower_bound
|
|
3270
|
+
self._upper_bound = upper_bound
|
|
3271
|
+
self._name = name
|
|
3272
|
+
if indices is not None:
|
|
3273
|
+
for i in indices:
|
|
3274
|
+
self[i] # creates component
|
|
3275
|
+
self._keys = indices
|
|
3276
|
+
|
|
3277
|
+
def __copy__(self):
|
|
3278
|
+
r"""
|
|
3279
|
+
Return a copy of ``self``.
|
|
3280
|
+
|
|
3281
|
+
EXAMPLES::
|
|
3282
|
+
|
|
3283
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3284
|
+
sage: pv = p.new_variable(nonnegative=True)
|
|
3285
|
+
sage: pv[0]
|
|
3286
|
+
x_0
|
|
3287
|
+
sage: pvc = copy(pv)
|
|
3288
|
+
sage: pvc[0]
|
|
3289
|
+
x_0
|
|
3290
|
+
sage: pv[1]
|
|
3291
|
+
x_1
|
|
3292
|
+
sage: pvc[1]
|
|
3293
|
+
x_2
|
|
3294
|
+
sage: p.number_of_variables()
|
|
3295
|
+
3
|
|
3296
|
+
"""
|
|
3297
|
+
return self.copy_for_mip(self.mip())
|
|
3298
|
+
|
|
3299
|
+
def __deepcopy__(self, memo={}):
|
|
3300
|
+
r"""
|
|
3301
|
+
Return a copy of ``self``.
|
|
3302
|
+
|
|
3303
|
+
EXAMPLES::
|
|
3304
|
+
|
|
3305
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3306
|
+
sage: pv = p.new_variable(nonnegative=True)
|
|
3307
|
+
sage: pv[0]
|
|
3308
|
+
x_0
|
|
3309
|
+
sage: pvc = deepcopy(pv)
|
|
3310
|
+
sage: pvc[0]
|
|
3311
|
+
x_0
|
|
3312
|
+
sage: pv[1]
|
|
3313
|
+
x_1
|
|
3314
|
+
sage: pvc[1]
|
|
3315
|
+
x_2
|
|
3316
|
+
sage: p.number_of_variables()
|
|
3317
|
+
3
|
|
3318
|
+
"""
|
|
3319
|
+
return self.copy_for_mip(self.mip())
|
|
3320
|
+
|
|
3321
|
+
def __getitem__(self, i):
|
|
3322
|
+
r"""
|
|
3323
|
+
Return the variable component corresponding to the key.
|
|
3324
|
+
|
|
3325
|
+
Returns the component asked.
|
|
3326
|
+
|
|
3327
|
+
Otherwise, if ``self`` was created with indices=None,
|
|
3328
|
+
creates the component.
|
|
3329
|
+
|
|
3330
|
+
EXAMPLES:
|
|
3331
|
+
|
|
3332
|
+
Dynamic indices::
|
|
3333
|
+
|
|
3334
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3335
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3336
|
+
sage: p.set_objective(v[0] + v[1])
|
|
3337
|
+
sage: v[0]
|
|
3338
|
+
x_0
|
|
3339
|
+
|
|
3340
|
+
Static indices::
|
|
3341
|
+
|
|
3342
|
+
sage: p = MixedIntegerLinearProgram()
|
|
3343
|
+
sage: x = p.new_variable(indices=range(7))
|
|
3344
|
+
sage: p.number_of_variables()
|
|
3345
|
+
7
|
|
3346
|
+
sage: x[3]
|
|
3347
|
+
x_3
|
|
3348
|
+
sage: x[11]
|
|
3349
|
+
Traceback (most recent call last):
|
|
3350
|
+
...
|
|
3351
|
+
IndexError: 11 does not index a component of MIPVariable with 7 real components
|
|
3352
|
+
|
|
3353
|
+
Indices can be more than just integers::
|
|
3354
|
+
|
|
3355
|
+
sage: p = MixedIntegerLinearProgram()
|
|
3356
|
+
sage: indices = ( (i,j) for i in range(6) for j in range(4) )
|
|
3357
|
+
sage: x = p.new_variable(indices=indices)
|
|
3358
|
+
sage: p.number_of_variables()
|
|
3359
|
+
24
|
|
3360
|
+
sage: x[(2, 3)]
|
|
3361
|
+
x_11
|
|
3362
|
+
|
|
3363
|
+
TESTS:
|
|
3364
|
+
|
|
3365
|
+
An empty list of static indices gives an error on every component access;
|
|
3366
|
+
it is different from passing ``indices=None`` (the default) on init. ::
|
|
3367
|
+
|
|
3368
|
+
sage: p = MixedIntegerLinearProgram()
|
|
3369
|
+
sage: x = p.new_variable(indices=[])
|
|
3370
|
+
sage: x[0]
|
|
3371
|
+
Traceback (most recent call last):
|
|
3372
|
+
...
|
|
3373
|
+
IndexError: 0 does not index a component of MIPVariable with 0 real components
|
|
3374
|
+
"""
|
|
3375
|
+
cdef int j
|
|
3376
|
+
if i in self._dictionary:
|
|
3377
|
+
return self._dictionary[i]
|
|
3378
|
+
if self._keys is not None:
|
|
3379
|
+
raise IndexError("{} does not index a component of {}".format(i, self))
|
|
3380
|
+
zero = self._p._backend.zero()
|
|
3381
|
+
name = self._name + "[" + str(i) + "]" if self._name else None
|
|
3382
|
+
|
|
3383
|
+
j = self._p._backend.add_variable(
|
|
3384
|
+
lower_bound=self._lower_bound,
|
|
3385
|
+
upper_bound=self._upper_bound,
|
|
3386
|
+
binary=(self._vtype == self._p.__BINARY),
|
|
3387
|
+
continuous=(self._vtype == self._p.__REAL),
|
|
3388
|
+
integer=(self._vtype == self._p.__INTEGER),
|
|
3389
|
+
obj=zero,
|
|
3390
|
+
name=name)
|
|
3391
|
+
v = self._p.linear_functions_parent()({j: 1})
|
|
3392
|
+
self._p._variables[v] = j
|
|
3393
|
+
self._dictionary[i] = v
|
|
3394
|
+
return v
|
|
3395
|
+
|
|
3396
|
+
def copy_for_mip(self, mip):
|
|
3397
|
+
r"""
|
|
3398
|
+
Return a copy of ``self`` suitable for a new :class:`MixedIntegerLinearProgram`
|
|
3399
|
+
instance ``mip``.
|
|
3400
|
+
|
|
3401
|
+
For this to make sense, ``mip`` should have been obtained as a copy of
|
|
3402
|
+
``self.mip()``.
|
|
3403
|
+
|
|
3404
|
+
EXAMPLES::
|
|
3405
|
+
|
|
3406
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3407
|
+
sage: pv = p.new_variable(nonnegative=True)
|
|
3408
|
+
sage: pv[0]
|
|
3409
|
+
x_0
|
|
3410
|
+
sage: q = copy(p)
|
|
3411
|
+
sage: qv = pv.copy_for_mip(q)
|
|
3412
|
+
sage: pv[77]
|
|
3413
|
+
x_1
|
|
3414
|
+
sage: p.number_of_variables()
|
|
3415
|
+
2
|
|
3416
|
+
sage: q.number_of_variables()
|
|
3417
|
+
1
|
|
3418
|
+
sage: qv[33]
|
|
3419
|
+
x_1
|
|
3420
|
+
sage: p.number_of_variables()
|
|
3421
|
+
2
|
|
3422
|
+
sage: q.number_of_variables()
|
|
3423
|
+
2
|
|
3424
|
+
|
|
3425
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3426
|
+
sage: pv = p.new_variable(indices=[3, 7])
|
|
3427
|
+
sage: q = copy(p)
|
|
3428
|
+
sage: qv = pv.copy_for_mip(q)
|
|
3429
|
+
sage: qv[3]
|
|
3430
|
+
x_0
|
|
3431
|
+
sage: qv[5]
|
|
3432
|
+
Traceback (most recent call last):
|
|
3433
|
+
...
|
|
3434
|
+
IndexError: 5 does not index a component of MIPVariable with 2 real components
|
|
3435
|
+
"""
|
|
3436
|
+
cdef MIPVariable cp = type(self)(mip, self._vtype, self._name,
|
|
3437
|
+
self._lower_bound, self._upper_bound)
|
|
3438
|
+
cp._dictionary = copy(self._dictionary)
|
|
3439
|
+
cp._keys = self._keys
|
|
3440
|
+
return cp
|
|
3441
|
+
|
|
3442
|
+
def set_min(self, min):
|
|
3443
|
+
r"""
|
|
3444
|
+
Set a lower bound on the variable.
|
|
3445
|
+
|
|
3446
|
+
INPUT:
|
|
3447
|
+
|
|
3448
|
+
- ``min`` -- a lower bound, or ``None`` to mean that the variable is
|
|
3449
|
+
unbounded
|
|
3450
|
+
|
|
3451
|
+
EXAMPLES::
|
|
3452
|
+
|
|
3453
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3454
|
+
sage: v = p.new_variable(real=True, nonnegative=True)
|
|
3455
|
+
sage: p.get_min(v)
|
|
3456
|
+
0
|
|
3457
|
+
sage: p.get_min(v[0])
|
|
3458
|
+
0.0
|
|
3459
|
+
sage: p.set_min(v,4)
|
|
3460
|
+
sage: p.get_min(v)
|
|
3461
|
+
4
|
|
3462
|
+
sage: p.get_min(v[0])
|
|
3463
|
+
4.0
|
|
3464
|
+
|
|
3465
|
+
TESTS:
|
|
3466
|
+
|
|
3467
|
+
Test that :issue:`20462` is fixed::
|
|
3468
|
+
|
|
3469
|
+
sage: p.<x,y> = MixedIntegerLinearProgram()
|
|
3470
|
+
sage: x[0], y[0]
|
|
3471
|
+
(x_0, x_1)
|
|
3472
|
+
sage: x.set_min(42)
|
|
3473
|
+
sage: p.get_min(y[0]) is None
|
|
3474
|
+
True
|
|
3475
|
+
"""
|
|
3476
|
+
self._lower_bound = min
|
|
3477
|
+
for v in self._dictionary.values():
|
|
3478
|
+
self._p.set_min(v,min)
|
|
3479
|
+
|
|
3480
|
+
def set_max(self, max):
|
|
3481
|
+
r"""
|
|
3482
|
+
Set an upper bound on the variable.
|
|
3483
|
+
|
|
3484
|
+
INPUT:
|
|
3485
|
+
|
|
3486
|
+
- ``max`` -- an upper bound, or ``None`` to mean that the variable is
|
|
3487
|
+
unbounded
|
|
3488
|
+
|
|
3489
|
+
EXAMPLES::
|
|
3490
|
+
|
|
3491
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3492
|
+
sage: v = p.new_variable(real=True, nonnegative=True)
|
|
3493
|
+
sage: p.get_max(v)
|
|
3494
|
+
sage: p.get_max(v[0])
|
|
3495
|
+
sage: p.set_max(v,4)
|
|
3496
|
+
sage: p.get_max(v)
|
|
3497
|
+
4
|
|
3498
|
+
sage: p.get_max(v[0])
|
|
3499
|
+
4.0
|
|
3500
|
+
|
|
3501
|
+
TESTS:
|
|
3502
|
+
|
|
3503
|
+
Test that :issue:`20462` is fixed::
|
|
3504
|
+
|
|
3505
|
+
sage: p.<x,y> = MixedIntegerLinearProgram()
|
|
3506
|
+
sage: x[0], y[0]
|
|
3507
|
+
(x_0, x_1)
|
|
3508
|
+
sage: x.set_max(42)
|
|
3509
|
+
sage: p.get_max(y[0]) is None
|
|
3510
|
+
True
|
|
3511
|
+
"""
|
|
3512
|
+
self._upper_bound = max
|
|
3513
|
+
for v in self._dictionary.values():
|
|
3514
|
+
self._p.set_max(v,max)
|
|
3515
|
+
|
|
3516
|
+
def _repr_(self):
|
|
3517
|
+
r"""
|
|
3518
|
+
Return a representation of ``self``.
|
|
3519
|
+
|
|
3520
|
+
EXAMPLES::
|
|
3521
|
+
|
|
3522
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3523
|
+
sage: v = p.new_variable()
|
|
3524
|
+
sage: v
|
|
3525
|
+
MIPVariable with 0 real components
|
|
3526
|
+
sage: x = p.new_variable(integer=True, nonnegative=True, name='x')
|
|
3527
|
+
sage: x[0]
|
|
3528
|
+
x_0
|
|
3529
|
+
sage: x
|
|
3530
|
+
MIPVariable x with 1 integer component, >= 0
|
|
3531
|
+
sage: x[1]
|
|
3532
|
+
x_1
|
|
3533
|
+
sage: x
|
|
3534
|
+
MIPVariable x with 2 integer components, >= 0
|
|
3535
|
+
sage: y = p.new_variable(real=True, name='y', indices=range(5))
|
|
3536
|
+
sage: y.set_min(0)
|
|
3537
|
+
sage: y.set_max(17)
|
|
3538
|
+
sage: y
|
|
3539
|
+
MIPVariable y with 5 real components, >= 0, <= 17
|
|
3540
|
+
sage: z = p.new_variable(binary=True, name='z', indices=range(7))
|
|
3541
|
+
sage: z
|
|
3542
|
+
MIPVariable z with 7 binary components
|
|
3543
|
+
"""
|
|
3544
|
+
s = 'MIPVariable{0} with {1} {2} component{3}'.format(
|
|
3545
|
+
" " + self._name if self._name else "",
|
|
3546
|
+
len(self._dictionary),
|
|
3547
|
+
{0:"binary", -1:"real", 1:"integer"}[self._vtype],
|
|
3548
|
+
"s" if len(self._dictionary) != 1 else "")
|
|
3549
|
+
if (self._vtype != 0) and (self._lower_bound is not None):
|
|
3550
|
+
s += ', >= {0}'.format(self._lower_bound)
|
|
3551
|
+
if (self._vtype != 0) and (self._upper_bound is not None):
|
|
3552
|
+
s += ', <= {0}'.format(self._upper_bound)
|
|
3553
|
+
return s
|
|
3554
|
+
|
|
3555
|
+
def keys(self):
|
|
3556
|
+
r"""
|
|
3557
|
+
Return the keys already defined in the dictionary.
|
|
3558
|
+
|
|
3559
|
+
EXAMPLES::
|
|
3560
|
+
|
|
3561
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3562
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3563
|
+
sage: p.set_objective(v[0] + v[1])
|
|
3564
|
+
sage: sorted(v.keys())
|
|
3565
|
+
[0, 1]
|
|
3566
|
+
"""
|
|
3567
|
+
return self._dictionary.keys()
|
|
3568
|
+
|
|
3569
|
+
def items(self):
|
|
3570
|
+
r"""
|
|
3571
|
+
Return the pairs (keys, value) contained in the dictionary.
|
|
3572
|
+
|
|
3573
|
+
EXAMPLES::
|
|
3574
|
+
|
|
3575
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3576
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3577
|
+
sage: p.set_objective(v[0] + v[1])
|
|
3578
|
+
sage: sorted(v.items())
|
|
3579
|
+
[(0, x_0), (1, x_1)]
|
|
3580
|
+
"""
|
|
3581
|
+
return self._dictionary.items()
|
|
3582
|
+
|
|
3583
|
+
def values(self):
|
|
3584
|
+
r"""
|
|
3585
|
+
Return the symbolic variables associated to the current dictionary.
|
|
3586
|
+
|
|
3587
|
+
EXAMPLES::
|
|
3588
|
+
|
|
3589
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3590
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3591
|
+
sage: p.set_objective(v[0] + v[1])
|
|
3592
|
+
sage: sorted(v.values(), key=str)
|
|
3593
|
+
[x_0, x_1]
|
|
3594
|
+
"""
|
|
3595
|
+
return self._dictionary.values()
|
|
3596
|
+
|
|
3597
|
+
def mip(self):
|
|
3598
|
+
r"""
|
|
3599
|
+
Return the :class:`MixedIntegerLinearProgram` in which ``self`` is a variable.
|
|
3600
|
+
|
|
3601
|
+
EXAMPLES::
|
|
3602
|
+
|
|
3603
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3604
|
+
sage: v = p.new_variable(nonnegative=True)
|
|
3605
|
+
sage: p == v.mip()
|
|
3606
|
+
True
|
|
3607
|
+
"""
|
|
3608
|
+
return self._p
|
|
3609
|
+
|
|
3610
|
+
def __mul__(left, right):
|
|
3611
|
+
"""
|
|
3612
|
+
Multiply ``left`` with ``right``.
|
|
3613
|
+
|
|
3614
|
+
EXAMPLES::
|
|
3615
|
+
|
|
3616
|
+
sage: p = MixedIntegerLinearProgram(solver='GLPK')
|
|
3617
|
+
sage: v = p.new_variable()
|
|
3618
|
+
sage: m = matrix([[1,2], [3,4]])
|
|
3619
|
+
sage: v * m
|
|
3620
|
+
(1.0, 2.0)*x_0 + (3.0, 4.0)*x_1
|
|
3621
|
+
sage: m * v
|
|
3622
|
+
(1.0, 3.0)*x_0 + (2.0, 4.0)*x_1
|
|
3623
|
+
|
|
3624
|
+
sage: p = MixedIntegerLinearProgram(solver='PPL')
|
|
3625
|
+
sage: v = p.new_variable()
|
|
3626
|
+
sage: m = matrix([[1,1/2], [2/3,3/4]])
|
|
3627
|
+
sage: v * m
|
|
3628
|
+
(1, 1/2)*x_0 + (2/3, 3/4)*x_1
|
|
3629
|
+
sage: m * v
|
|
3630
|
+
(1, 2/3)*x_0 + (1/2, 3/4)*x_1
|
|
3631
|
+
"""
|
|
3632
|
+
if isinstance(left, MIPVariable):
|
|
3633
|
+
if not isinstance(right, Matrix):
|
|
3634
|
+
return NotImplemented
|
|
3635
|
+
return (<MIPVariable> left)._matrix_rmul_impl(right)
|
|
3636
|
+
else:
|
|
3637
|
+
if not isinstance(left, Matrix):
|
|
3638
|
+
return NotImplemented
|
|
3639
|
+
return (<MIPVariable> right)._matrix_lmul_impl(left)
|
|
3640
|
+
|
|
3641
|
+
cdef _matrix_rmul_impl(self, m):
|
|
3642
|
+
"""
|
|
3643
|
+
Implement the action of a matrix multiplying from the right.
|
|
3644
|
+
"""
|
|
3645
|
+
result = dict()
|
|
3646
|
+
for i, row in enumerate(m.rows()):
|
|
3647
|
+
x = self[i]
|
|
3648
|
+
x_index, = x.dict().keys()
|
|
3649
|
+
result[x_index] = row
|
|
3650
|
+
from sage.modules.free_module import FreeModule
|
|
3651
|
+
V = FreeModule(self._p.base_ring(), m.ncols())
|
|
3652
|
+
T = self._p.linear_functions_parent().tensor(V)
|
|
3653
|
+
return T(result)
|
|
3654
|
+
|
|
3655
|
+
cdef _matrix_lmul_impl(self, m):
|
|
3656
|
+
"""
|
|
3657
|
+
Implement the action of a matrix multiplying from the left.
|
|
3658
|
+
"""
|
|
3659
|
+
result = {}
|
|
3660
|
+
for i, col in enumerate(m.columns()):
|
|
3661
|
+
x = self[i]
|
|
3662
|
+
x_index, = x.dict().keys()
|
|
3663
|
+
result[x_index] = col
|
|
3664
|
+
from sage.modules.free_module import FreeModule
|
|
3665
|
+
V = FreeModule(self._p.base_ring(), m.nrows())
|
|
3666
|
+
T = self._p.linear_functions_parent().tensor(V)
|
|
3667
|
+
return T(result)
|