This repository has been archived by the owner on Jan 30, 2023. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
1,458 additions
and
0 deletions.
There are no files selected for viewing
367 changes: 367 additions & 0 deletions
367
src/sage/numerical/backends/abstract_backend_dictionary.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,367 @@ | ||
from sage.numerical.interactive_simplex_method import * | ||
|
||
|
||
class LPAbstractBackendDictionary(LPAbstractDictionary): | ||
r""" | ||
Construct an abstract dictionary for an LP problem from an backend. | ||
INPUT: | ||
- ``backend`` -- the backend where the dictionary is | ||
constructed from | ||
OUTPUT: | ||
- a :class:`backend dictionary for an LP problem | ||
<LPAbstractBackendDictionary>` | ||
EXAMPLES: | ||
One needs an instance of :class:`MixedIntegerLinearProgram` to initialize | ||
this class:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True) | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: d = LPAbstractBackendDictionary(b) | ||
sage: d | ||
LP problem dictionary (use typeset mode to see details) | ||
""" | ||
def __init__(self, backend): | ||
super(LPAbstractBackendDictionary, self).__init__() | ||
self._backend = backend | ||
for i in range(self._backend.nrows()): | ||
if self._backend.row_bounds(i)[0] != None \ | ||
or self._backend.row_bounds(i)[1] == None: | ||
raise AttributeError("Problem constraints " | ||
"not in standard form.") | ||
|
||
for i in range(self._backend.ncols()): | ||
if self._backend.variable_lower_bound(i) == None: | ||
raise AttributeError("Problem variables " | ||
"not in standard form.") | ||
|
||
col_vars = tuple( | ||
LPAbstractBackendDictionary._format_( | ||
name=self._backend.col_name(i), | ||
symbol='x', index=i | ||
) | ||
for i in range(self._backend.ncols()) | ||
) | ||
row_vars = tuple( | ||
LPAbstractBackendDictionary._format_( | ||
name=self._backend.row_name(i), | ||
symbol='w', index=i | ||
) | ||
for i in range(self._backend.nrows()) | ||
) | ||
self._names = ", ".join(col_vars + row_vars) | ||
self._R = PolynomialRing(self._backend.base_ring(), | ||
self._names, order="neglex") | ||
self._x = self._R.gens() | ||
|
||
def __eq__(self, other): | ||
r""" | ||
Check if two LP problem dictionaries have the same | ||
reference. | ||
INPUT: | ||
- ``other`` -- anything | ||
OUTPUT: | ||
- ``True`` if ``other`` is an :class:`LPDictionary` with its | ||
reference the same as ``self``, ``False`` otherwise. | ||
TESTS: | ||
Setting up the problem:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True,\ | ||
solver="Coin") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: d = LPAbstractBackendDictionary(b) | ||
Test when two problems have the same reference: | ||
sage: d2 = d | ||
sage: d2 == d | ||
True | ||
Test when two problems have the same constrct: | ||
sage: d3 = LPAbstractBackendDictionary(copy(p).get_backend()) | ||
sage: d3 == d | ||
False | ||
""" | ||
return (isinstance(other, LPAbstractBackendDictionary) and | ||
self._backend == other._backend) | ||
|
||
@staticmethod | ||
def _format_(name='', symbol='x', infix='_', index='0'): | ||
r""" | ||
Returns a proper name for a given parameters. | ||
INPUT:: | ||
- ``name`` -- (defualt: '') the original name of the variable | ||
-``symbol`` -- (defualt: 'x') the symbol for the new name | ||
-``infix`` -- (default: '_') the character separate the symbol | ||
and its index for the new name | ||
-``index`` -- (default: '0') the index following the infix for | ||
the new name | ||
TESTS: | ||
If a name is given, then returns the name itself:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: LPAbstractBackendDictionary._format_('Name') | ||
'Name' | ||
However, if the name given is in the form 'symbol[index]', then it | ||
will be converted to 'symbol+infix+index' i.e. 'symbol_index' | ||
by default: | ||
sage: LPAbstractBackendDictionary._format_('x[3]') | ||
'x_3' | ||
sage: LPAbstractBackendDictionary._format_('x[2]', infix='~') | ||
'x~2' | ||
If no name is given, then a newly create name will be returned in the | ||
form of 'symbol+infix+index': | ||
sage: LPAbstractBackendDictionary._format_(symbol='w', index='7') | ||
'w_7' | ||
""" | ||
if name: | ||
return name.replace('[', infix).strip(']') | ||
else: | ||
return symbol + infix + str(index) | ||
|
||
def objective_value(self): | ||
r""" | ||
Return the value of the objective value. | ||
OUTPUT: | ||
- a number | ||
EXAMPLES:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True,\ | ||
solver="Coin") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: b.solve() | ||
0 | ||
sage: d = LPAbstractBackendDictionary(b) | ||
sage: d.objective_value() | ||
14.08 | ||
""" | ||
return self._backend.get_objective_value() | ||
|
||
def get_backend(self): | ||
r""" | ||
Return the backend used to create the dictionary. | ||
OUTPUT: | ||
- The corresponding backend associated with self | ||
EXAMPLES:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True,\ | ||
solver="Coin") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: b.solve() | ||
0 | ||
sage: d = LPAbstractBackendDictionary(b) | ||
sage: d.get_backend() | ||
<sage.numerical.backends.coin_backend.CoinBackend object at ...> | ||
""" | ||
return self._backend | ||
|
||
def dictionary(self): | ||
r""" | ||
Return a regular LP dictionary matching ``self``. | ||
OUTPUT: | ||
- an :class:`LP dictionary <LPDictionary>` | ||
EXAMPLES:: | ||
sage: from sage.numerical.backends.glpk_backend_dictionary \ | ||
import LPGLPKBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True, \ | ||
solver="GLPK") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: import sage.numerical.backends.glpk_backend as backend | ||
sage: b.solver_parameter(\ | ||
backend.glp_simplex_or_intopt, backend.glp_simplex_only) | ||
sage: b.solve() | ||
0 | ||
sage: d = LPGLPKBackendDictionary(b) | ||
sage: view(d.dictionary()) # not tested | ||
Zeta is used as default problem name, and it can be changed: | ||
sage: b.problem_name("beta") | ||
sage: view(d.dictionary()) # not tested | ||
""" | ||
rows = [] | ||
for i in range(self._backend.nrows()): | ||
self.leave(self.basic_variables()[i]) | ||
rows.append(self.leaving_coefficients()) | ||
import sage.matrix.constructor as construc | ||
m = construc.matrix(rows) | ||
name = self._backend.problem_name() | ||
if not name: | ||
name = 'zeta' | ||
D = LPDictionary(m, | ||
self.constant_terms(), | ||
self.objective_coefficients(), | ||
self.objective_value(), | ||
self.basic_variables(), | ||
self.nonbasic_variables(), | ||
name) | ||
D._entering = self._entering | ||
D._leaving = self._leaving | ||
return D | ||
|
||
def _load_new_variable_(self, index, name, auxiliary=True): | ||
r""" | ||
Load a new variable to buffer. | ||
INPUT: | ||
- ``index`` -- the index of the new variable | ||
- ``name`` -- the name of the new variable | ||
- ``auxiliary`` -- (default: True) if true, the symbol of the | ||
new varible will be 'w', 'x' otherwise. | ||
EXAMPLE:: | ||
sage: from sage.numerical.backends.abstract_backend_dictionary \ | ||
import LPAbstractBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True,\ | ||
solver="Coin") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: b.solve() | ||
0 | ||
sage: d = LPAbstractBackendDictionary(b) | ||
sage: d._x | ||
(x_0, x_1, w_0, w_1) | ||
""" | ||
if auxiliary: | ||
symbol = 'w' | ||
else: | ||
symbol = 'x' | ||
|
||
self._names += ', ' | ||
self._names += LPAbstractBackendDictionary._format_( | ||
name=name, symbol=symbol, index=index | ||
) | ||
self._R = PolynomialRing(self._backend.base_ring(), | ||
self._names, order="neglex") | ||
self._x = self._R.gens() | ||
|
||
def _nonbasic_coef_to_problem_coef_(self, nonbasic_coef, constant): | ||
r""" | ||
Returns coefficients of nonbasic variables rewritten in terms of | ||
problem variables. | ||
INPUT: | ||
- ``nonbasic_coef`` -- a list of nonbasic coefficients for the new row | ||
- ``constant`` -- a number of the constant term for the new row | ||
OUTPUT: | ||
- ``coef_pairs`` -- a list of tuples that indicate problem variable | ||
indices and coefficients | ||
- ``constant`` -- the re-calculated row bound | ||
EXAMPLE:: | ||
sage: from sage.numerical.backends.glpk_backend_dictionary \ | ||
import LPGLPKBackendDictionary | ||
sage: p = MixedIntegerLinearProgram(maximization=True, \ | ||
solver="GLPK") | ||
sage: x = p.new_variable(nonnegative=True) | ||
sage: p.add_constraint(-x[0] + x[1] <= 2) | ||
sage: p.add_constraint(8 * x[0] + 2 * x[1] <= 17) | ||
sage: p.set_objective(5.5 * x[0] + 2.1 * x[1]) | ||
sage: b = p.get_backend() | ||
sage: import sage.numerical.backends.glpk_backend as backend | ||
sage: b.solver_parameter(\ | ||
backend.glp_simplex_or_intopt, backend.glp_simplex_only) | ||
sage: b.solve() | ||
0 | ||
sage: d = LPGLPKBackendDictionary(b) | ||
sage: nonbasic_coef = [3, 4, 5, 6] | ||
sage: constant = 11 | ||
sage: pairs, const = \ | ||
d._nonbasic_coef_to_problem_coef_(nonbasic_coef, constant) | ||
sage: pairs | ||
[(0, -29.0), (1, -11.0)] | ||
sage: const | ||
-63.0 | ||
""" | ||
coefs = [0] * self._backend.ncols() | ||
for i, var in enumerate(self.nonbasic_variables()): | ||
index = self._x.index(var) | ||
if index < self._backend.ncols(): | ||
coefs[index] += nonbasic_coef[i] | ||
else: | ||
row_pos = index - self._backend.ncols() | ||
row = self._backend.row(row_pos) | ||
for j, v in zip(*row): | ||
coefs[j] -= nonbasic_coef[i] * v | ||
upper_bound = self._backend.row_bounds(row_pos)[1] | ||
constant -= nonbasic_coef[i] * upper_bound | ||
|
||
coef_pairs = [(i, coefs[i]) for i in range(self._backend.ncols()) | ||
if coefs[i] != 0] | ||
return coef_pairs, constant | ||
# just find common backend functions in coin and glpk? |
Oops, something went wrong.