Path: blob/master/src/sage/numerical/backends/ppl_backend.pyx
8817 views
"""1PPL Backend23AUTHORS:45- Risan (2012-02): initial implementation6"""78#*****************************************************************************9# Copyright (C) 2010 Risan <[email protected]>10#11# Distributed under the terms of the GNU General Public License (GPL)12# as published by the Free Software Foundation; either version 2 of13# the License, or (at your option) any later version.14# http://www.gnu.org/licenses/15#*****************************************************************************1617from sage.numerical.mip import MIPSolverException18from sage.libs.ppl import MIP_Problem, Variable, Linear_Expression, Constraint, Generator1920cdef class PPLBackend(GenericBackend):21cdef object mip22cdef list Matrix23cdef list row_lower_bound24cdef list row_upper_bound25cdef list col_lower_bound26cdef list col_upper_bound27cdef list objective_function28cdef list row_name_var29cdef list col_name_var30cdef int is_maximize31cdef str name3233def __cinit__(self, maximization = True):34"""35Constructor3637EXAMPLE::3839sage: p = MixedIntegerLinearProgram(solver = "PPL")40"""4142self.Matrix = []43self.row_lower_bound = []44self.row_upper_bound = []45self.col_lower_bound = []46self.col_upper_bound = []47self.objective_function = []48self.row_name_var = []49self.col_name_var = []50self.name = ''51self.obj_constant_term = 0;5253if maximization:54self.set_sense(+1)55else:56self.set_sense(-1)5758cpdef base_ring(self):59from sage.rings.all import QQ60return QQ6162cpdef zero(self):63return self.base_ring()(0)6465def init_mip(self):66"""67Converting the matrix form of the MIP Problem to PPL MIP_Problem.6869EXAMPLE::7071sage: from sage.numerical.backends.generic_backend import get_solver72sage: p = get_solver(solver = "PPL")73sage: p.base_ring()74Rational Field75sage: type(p.zero())76<type 'sage.rings.rational.Rational'>77sage: p.init_mip()7879"""8081self.mip = MIP_Problem()82mip_obj = Linear_Expression(0)8384self.mip.add_space_dimensions_and_embed(len(self.objective_function))85for i in range(len(self.objective_function)):86mip_obj = mip_obj + Linear_Expression(self.objective_function[i] * Variable(i))87self.mip.set_objective_function(mip_obj)88for i in range(len(self.Matrix)):89l = Linear_Expression(0)90for j in range(len(self.Matrix[i])):91l = l + Linear_Expression(self.Matrix[i][j] * Variable(j))92if self.row_lower_bound[i] is not None:93self.mip.add_constraint(l >= self.row_lower_bound[i])94if self.row_upper_bound[i] is not None:95self.mip.add_constraint(l <= self.row_upper_bound[i])9697for i in range(len(self.col_lower_bound)):98if self.col_lower_bound[i] is not None:99self.mip.add_constraint(Variable(i) >= self.col_lower_bound[i])100101for i in range(len(self.col_upper_bound)):102if self.col_upper_bound[i] is not None:103self.mip.add_constraint(Variable(i) <= self.col_upper_bound[i])104105if self.is_maximize == 1:106self.mip.set_optimization_mode('maximization')107else:108self.mip.set_optimization_mode('minimization')109110cpdef int add_variable(self, lower_bound=0, upper_bound=None, binary=False, continuous=True, integer=False, obj=0, name=None) except -1:111"""112Add a variable.113114This amounts to adding a new column to the matrix. By default,115the variable is both positive and real.116117It has not been implemented for selecting the variable type yet.118119INPUT:120121- ``lower_bound`` -- the lower bound of the variable (default: 0)122123- ``upper_bound`` -- the upper bound of the variable (default: ``None``)124125- ``binary`` -- ``True`` if the variable is binary (default: ``False``).126127- ``continuous`` -- ``True`` if the variable is binary (default: ``True``).128129- ``integer`` -- ``True`` if the variable is binary (default: ``False``).130131- ``obj`` -- (optional) coefficient of this variable in the objective function (default: 0)132133- ``name`` -- an optional name for the newly added variable (default: ``None``).134135OUTPUT: The index of the newly created variable136137EXAMPLE::138139sage: from sage.numerical.backends.generic_backend import get_solver140sage: p = get_solver(solver = "PPL")141sage: p.ncols()1420143sage: p.add_variable()1440145sage: p.ncols()1461147sage: p.add_variable(lower_bound=-2)1481149sage: p.add_variable(name='x',obj=2/3)1502151sage: p.col_name(2)152'x'153sage: p.objective_coefficient(2)1542/3155"""156for i in range(len(self.Matrix)):157self.Matrix[i].append(0)158self.col_lower_bound.append(lower_bound)159self.col_upper_bound.append(upper_bound)160self.objective_function.append(obj)161self.col_name_var.append(name)162return len(self.objective_function) - 1163164cpdef int add_variables(self, int n, lower_bound=0, upper_bound=None, binary=False, continuous=True, integer=False, obj=0, names=None) except -1:165"""166Add ``n`` variables.167168This amounts to adding new columns to the matrix. By default,169the variables are both positive and real.170171It has not been implemented for selecting the variable type yet.172173INPUT:174175- ``n`` -- the number of new variables (must be > 0)176177- ``lower_bound`` -- the lower bound of the variable (default: 0)178179- ``upper_bound`` -- the upper bound of the variable (default: ``None``)180181- ``binary`` -- ``True`` if the variable is binary (default: ``False``).182183- ``continuous`` -- ``True`` if the variable is binary (default: ``True``).184185- ``integer`` -- ``True`` if the variable is binary (default: ``False``).186187- ``obj`` -- (optional) coefficient of all variables in the objective function (default: 0)188189- ``names`` -- optional list of names (default: ``None``)190191OUTPUT: The index of the variable created last.192193EXAMPLE::194195sage: from sage.numerical.backends.generic_backend import get_solver196sage: p = get_solver(solver = "PPL")197sage: p.ncols()1980199sage: p.add_variables(5)2004201sage: p.ncols()2025203sage: p.add_variables(2, lower_bound=-2.0, names=['a','b'])2046205"""206for k in range(n):207for i in range(len(self.Matrix)):208self.Matrix[i].append(0)209self.col_lower_bound.append(lower_bound)210self.col_upper_bound.append(upper_bound)211self.objective_function.append(obj)212if names is not None:213self.col_name_var.append(names[k])214else:215self.col_name_var.append(None)216return len(self.objective_function) - 1;217218cpdef set_variable_type(self, int variable, int vtype):219"""220Set the type of a variable.221222EXAMPLE::223224sage: from sage.numerical.backends.generic_backend import get_solver225sage: p = get_solver(solver = "PPL")226sage: p.add_variables(5)2274228sage: p.set_variable_type(3, -1)229sage: p.set_variable_type(3, -2)230Traceback (most recent call last):231...232Exception: ...233"""234if vtype != -1:235raise Exception('This backend does not handle integer variables ! Read the doc !')236237cpdef set_sense(self, int sense):238"""239Set the direction (maximization/minimization).240241INPUT:242243- ``sense`` (integer) :244245* +1 => Maximization246* -1 => Minimization247248EXAMPLE::249250sage: from sage.numerical.backends.generic_backend import get_solver251sage: p = get_solver(solver = "PPL")252sage: p.is_maximization()253True254sage: p.set_sense(-1)255sage: p.is_maximization()256False257"""258if sense == 1:259self.is_maximize = 1260else:261self.is_maximize = 0262263cpdef objective_coefficient(self, int variable, coeff=None):264"""265Set or get the coefficient of a variable in the objective266function267268INPUT:269270- ``variable`` (integer) -- the variable's id271272- ``coeff`` (integer) -- its coefficient273274EXAMPLE::275276sage: from sage.numerical.backends.generic_backend import get_solver277sage: p = get_solver(solver = "PPL")278sage: p.add_variable()2790280sage: p.objective_coefficient(0)2810282sage: p.objective_coefficient(0,2)283sage: p.objective_coefficient(0)2842285"""286if coeff is not None:287self.objective_function[variable] = coeff;288else:289return self.objective_function[variable]290291cpdef set_objective(self, list coeff, d = 0):292"""293Set the objective function.294295INPUT:296297- ``coeff`` -- a list of real values, whose ith element is the298coefficient of the ith variable in the objective function.299300EXAMPLE::301302sage: from sage.numerical.backends.generic_backend import get_solver303sage: p = get_solver(solver = "PPL")304sage: p.add_variables(5)3054306sage: p.set_objective([1, 1, 2, 1, 3])307sage: map(lambda x :p.objective_coefficient(x), range(5))308[1, 1, 2, 1, 3]309"""310for i in range(len(coeff)):311self.objective_function[i] = coeff[i];312obj_constant_term = d;313314cpdef set_verbosity(self, int level):315"""316Set the log (verbosity) level. Not Implemented.317318EXAMPLE::319320sage: from sage.numerical.backends.generic_backend import get_solver321sage: p = get_solver(solver = "PPL")322sage: p.set_verbosity(0)323"""324325cpdef add_linear_constraint(self, coefficients, lower_bound, upper_bound, name=None):326"""327Add a linear constraint.328329INPUT:330331- ``coefficients`` -- an iterable with ``(c,v)`` pairs where ``c``332is a variable index (integer) and ``v`` is a value (real333value).334335- ``lower_bound`` -- a lower bound, either a real value or ``None``336337- ``upper_bound`` -- an upper bound, either a real value or ``None``338339- ``name`` -- an optional name for this row (default: ``None``)340341EXAMPLE::342343sage: from sage.numerical.backends.generic_backend import get_solver344sage: p = get_solver(solver = "PPL")345sage: p.add_variables(5)3464347sage: p.add_linear_constraint(zip(range(5), range(5)), 2.0, 2.0)348sage: p.row(0)349([1, 2, 3, 4], [1, 2, 3, 4])350sage: p.row_bounds(0)351(2.00000000000000, 2.00000000000000)352sage: p.add_linear_constraint( zip(range(5), range(5)), 1.0, 1.0, name='foo')353sage: p.row_name(-1)354'foo'355"""356last = len(self.Matrix)357self.Matrix.append([])358for i in range(len(self.objective_function)):359self.Matrix[last].append(0)360for a in coefficients:361self.Matrix[last][a[0]] = a[1]362363self.row_lower_bound.append(lower_bound)364self.row_upper_bound.append(upper_bound)365self.row_name_var.append(name)366367cpdef add_col(self, list indices, list coeffs):368"""369Add a column.370371INPUT:372373- ``indices`` (list of integers) -- this list constains the374indices of the constraints in which the variable's375coefficient is nonzero376377- ``coeffs`` (list of real values) -- associates a coefficient378to the variable in each of the constraints in which it379appears. Namely, the ith entry of ``coeffs`` corresponds to380the coefficient of the variable in the constraint381represented by the ith entry in ``indices``.382383.. NOTE::384385``indices`` and ``coeffs`` are expected to be of the same386length.387388EXAMPLE::389390sage: from sage.numerical.backends.generic_backend import get_solver391sage: p = get_solver(solver = "PPL")392sage: p.ncols()3930394sage: p.nrows()3950396sage: p.add_linear_constraints(5, 0, None)397sage: p.add_col(range(5), range(5))398sage: p.nrows()3995400"""401for i in range(len(self.Matrix)):402self.Matrix[i].append(0)403for i in range(len(indices)):404self.Matrix[indices[i]][len(self.Matrix[indices[i]]) - 1] = coeffs[i]405406self.col_lower_bound.append(None)407self.col_upper_bound.append(None)408self.objective_function.append(0)409self.col_name_var.append(None)410411cpdef add_linear_constraints(self, int number, lower_bound, upper_bound, names=None):412"""413Add constraints.414415INPUT:416417- ``number`` (integer) -- the number of constraints to add.418419- ``lower_bound`` -- a lower bound, either a real value or ``None``420421- ``upper_bound`` -- an upper bound, either a real value or ``None``422423- ``names`` -- an optional list of names (default: ``None``)424425EXAMPLE::426427sage: from sage.numerical.backends.generic_backend import get_solver428sage: p = get_solver(solver = "PPL")429sage: p.add_variables(5)4304431sage: p.add_linear_constraints(5, None, 2)432sage: p.row(4)433([], [])434sage: p.row_bounds(4)435(None, 2)436"""437for i in range(number):438self.Matrix.append([])439for j in range(len(self.objective_function)):440self.Matrix[i].append(0)441self.row_lower_bound.append(lower_bound)442self.row_upper_bound.append(upper_bound)443if names is not None:444self.row_name_var.append(names)445else:446self.row_name_var.append(None)447448cpdef int solve(self) except -1:449"""450Solve the problem.451452.. NOTE::453454This method raises ``MIPSolverException`` exceptions when455the solution can not be computed for any reason (none456exists, or the LP solver was not able to find it, etc...)457458EXAMPLE::459460sage: from sage.numerical.backends.generic_backend import get_solver461sage: p = get_solver(solver = "PPL")462sage: p.add_linear_constraints(5, 0, None)463sage: p.add_col(range(5), range(5))464sage: p.solve()4650466sage: p.objective_coefficient(0,1)467sage: p.solve()468Traceback (most recent call last):469...470MIPSolverException: ...471"""472self.init_mip()473474ans = self.mip.solve()475476if ans['status'] == 'optimized':477pass478elif ans['status'] == 'unbounded':479raise MIPSolverException("PPL : Solution is unbounded")480elif ans['status'] == 'unfeasible':481raise MIPSolverException("PPL : There is no feasible solution")482483return 0484485cpdef get_objective_value(self):486"""487Return the exact value of the objective function.488489.. NOTE::490491Behaviour is undefined unless ``solve`` has been called before.492493EXAMPLE::494495sage: from sage.numerical.backends.generic_backend import get_solver496sage: p = get_solver(solver = "PPL")497sage: p.add_variables(2)4981499sage: p.add_linear_constraint([(0,1), (1,2)], None, 3)500sage: p.set_objective([2, 5])501sage: p.solve()5020503sage: p.get_objective_value()50415/2505sage: p.get_variable_value(0)5060507sage: p.get_variable_value(1)5083/2509"""510self.init_mip()511ans = self.mip.optimal_value()512return ans + self.obj_constant_term513514cpdef get_variable_value(self, int variable):515"""516Return the value of a variable given by the solver.517518.. NOTE::519520Behaviour is undefined unless ``solve`` has been called before.521522EXAMPLE::523524sage: from sage.numerical.backends.generic_backend import get_solver525sage: p = get_solver(solver = "PPL")526sage: p.add_variables(2)5271528sage: p.add_linear_constraint([(0,1), (1, 2)], None, 3)529sage: p.set_objective([2, 5])530sage: p.solve()5310532sage: p.get_objective_value()53315/2534sage: p.get_variable_value(0)5350536sage: p.get_variable_value(1)5373/2538"""539self.init_mip()540g = self.mip.optimizing_point()541return g.coefficient(Variable(variable)) / g.divisor()542543cpdef int ncols(self):544"""545Return the number of columns/variables.546547EXAMPLE::548549sage: from sage.numerical.backends.generic_backend import get_solver550sage: p = get_solver(solver = "PPL")551sage: p.ncols()5520553sage: p.add_variables(2)5541555sage: p.ncols()5562557"""558return len(self.objective_function)559560cpdef int nrows(self):561"""562Return the number of rows/constraints.563564EXAMPLE::565566sage: from sage.numerical.backends.generic_backend import get_solver567sage: p = get_solver(solver = "PPL")568sage: p.nrows()5690570sage: p.add_linear_constraints(2, 2.0, None)571sage: p.nrows()5722573"""574return len(self.Matrix)575576cpdef bint is_maximization(self):577"""578Test whether the problem is a maximization579580EXAMPLE::581582sage: from sage.numerical.backends.generic_backend import get_solver583sage: p = get_solver(solver = "PPL")584sage: p.is_maximization()585True586sage: p.set_sense(-1)587sage: p.is_maximization()588False589"""590if self.is_maximize == 1:591return 1592else:593return 0594595cpdef problem_name(self, char * name = NULL):596"""597Return or define the problem's name598599INPUT:600601- ``name`` (``char *``) -- the problem's name. When set to602``NULL`` (default), the method returns the problem's name.603604EXAMPLE::605606sage: from sage.numerical.backends.generic_backend import get_solver607sage: p = get_solver(solver = "PPL")608sage: p.problem_name("There once was a french fry")609sage: print p.problem_name()610There once was a french fry611"""612if name == NULL:613return self.name614self.name = str(<bytes>name)615616cpdef row(self, int i):617"""618Return a row619620INPUT:621622- ``index`` (integer) -- the constraint's id.623624OUTPUT:625626A pair ``(indices, coeffs)`` where ``indices`` lists the627entries whose coefficient is nonzero, and to which ``coeffs``628associates their coefficient on the model of the629``add_linear_constraint`` method.630631EXAMPLE::632633sage: from sage.numerical.backends.generic_backend import get_solver634sage: p = get_solver(solver = "PPL")635sage: p.add_variables(5)6364637sage: p.add_linear_constraint(zip(range(5), range(5)), 2, 2)638sage: p.row(0)639([1, 2, 3, 4], [1, 2, 3, 4])640sage: p.row_bounds(0)641(2, 2)642"""643idx = []644coef = []645for j in range(len(self.Matrix[i])):646if self.Matrix[i][j] != 0:647idx.append(j)648coef.append(self.Matrix[i][j])649return (idx, coef)650651cpdef row_bounds(self, int index):652"""653Return the bounds of a specific constraint.654655INPUT:656657- ``index`` (integer) -- the constraint's id.658659OUTPUT:660661A pair ``(lower_bound, upper_bound)``. Each of them can be set662to ``None`` if the constraint is not bounded in the663corresponding direction, and is a real value otherwise.664665EXAMPLE::666667sage: from sage.numerical.backends.generic_backend import get_solver668sage: p = get_solver(solver = "PPL")669sage: p.add_variables(5)6704671sage: p.add_linear_constraint(zip(range(5), range(5)), 2, 2)672sage: p.row(0)673([1, 2, 3, 4], [1, 2, 3, 4])674sage: p.row_bounds(0)675(2, 2)676"""677return (self.row_lower_bound[index], self.row_upper_bound[index])678679cpdef col_bounds(self, int index):680"""681Return the bounds of a specific variable.682683INPUT:684685- ``index`` (integer) -- the variable's id.686687OUTPUT:688689A pair ``(lower_bound, upper_bound)``. Each of them can be set690to ``None`` if the variable is not bounded in the691corresponding direction, and is a real value otherwise.692693EXAMPLE::694695sage: from sage.numerical.backends.generic_backend import get_solver696sage: p = get_solver(solver = "PPL")697sage: p.add_variable()6980699sage: p.col_bounds(0)700(0, None)701sage: p.variable_upper_bound(0, 5)702sage: p.col_bounds(0)703(0, 5)704"""705return (self.col_lower_bound[index], self.col_upper_bound[index])706707708cpdef bint is_variable_binary(self, int index):709"""710Test whether the given variable is of binary type.711712INPUT:713714- ``index`` (integer) -- the variable's id715716EXAMPLE::717718sage: from sage.numerical.backends.generic_backend import get_solver719sage: p = get_solver(solver = "PPL")720sage: p.ncols()7210722sage: p.add_variable()7230724sage: p.is_variable_binary(0)725False726"""727return False728729cpdef bint is_variable_integer(self, int index):730"""731Test whether the given variable is of integer type.732733INPUT:734735- ``index`` (integer) -- the variable's id736737EXAMPLE::738739sage: from sage.numerical.backends.generic_backend import get_solver740sage: p = get_solver(solver = "PPL")741sage: p.ncols()7420743sage: p.add_variable()7440745sage: p.is_variable_integer(0)746False747"""748return False749750cpdef bint is_variable_continuous(self, int index):751"""752Test whether the given variable is of continuous/real type.753754INPUT:755756- ``index`` (integer) -- the variable's id757758EXAMPLE::759760sage: from sage.numerical.backends.generic_backend import get_solver761sage: p = get_solver(solver = "PPL")762sage: p.ncols()7630764sage: p.add_variable()7650766sage: p.is_variable_continuous(0)767True768"""769return True770771cpdef row_name(self, int index):772"""773Return the ``index`` th row name774775INPUT:776777- ``index`` (integer) -- the row's id778779EXAMPLE::780781sage: from sage.numerical.backends.generic_backend import get_solver782sage: p = get_solver(solver = "PPL")783sage: p.add_linear_constraints(1, 2, None, names="Empty constraint 1")784sage: p.row_name(0)785'Empty constraint 1'786"""787if self.row_name_var[index] is not None:788return self.row_name_var[index]789return "constraint_" + repr(index)790791cpdef col_name(self, int index):792"""793Return the ``index`` th col name794795INPUT:796797- ``index`` (integer) -- the col's id798799- ``name`` (``char *``) -- its name. When set to ``NULL``800(default), the method returns the current name.801802EXAMPLE::803804sage: from sage.numerical.backends.generic_backend import get_solver805sage: p = get_solver(solver = "PPL")806sage: p.add_variable(name="I am a variable")8070808sage: p.col_name(0)809'I am a variable'810"""811if self.col_name_var[index] is not None:812return self.col_name_var[index]813return "x_" + repr(index)814815cpdef variable_upper_bound(self, int index, value = False):816"""817Return or define the upper bound on a variable818819INPUT:820821- ``index`` (integer) -- the variable's id822823- ``value`` -- real value, or ``None`` to mean that the824variable has not upper bound. When set to ``None``825(default), the method returns the current value.826827EXAMPLE::828829sage: from sage.numerical.backends.generic_backend import get_solver830sage: p = get_solver(solver = "PPL")831sage: p.add_variable()8320833sage: p.col_bounds(0)834(0, None)835sage: p.variable_upper_bound(0, 5)836sage: p.col_bounds(0)837(0, 5)838sage: p.variable_upper_bound(0, None)839sage: p.col_bounds(0)840(0, None)841"""842if value is not False:843self.col_upper_bound[index] = value844else:845return self.col_upper_bound[index]846847cpdef variable_lower_bound(self, int index, value = False):848"""849Return or define the lower bound on a variable850851INPUT:852853- ``index`` (integer) -- the variable's id854855- ``value`` -- real value, or ``None`` to mean that the856variable has not lower bound. When set to ``None``857(default), the method returns the current value.858859EXAMPLE::860861sage: from sage.numerical.backends.generic_backend import get_solver862sage: p = get_solver(solver = "PPL")863sage: p.add_variable()8640865sage: p.col_bounds(0)866(0, None)867sage: p.variable_lower_bound(0, 5)868sage: p.col_bounds(0)869(5, None)870sage: p.variable_lower_bound(0, None)871sage: p.col_bounds(0)872(None, None)873"""874if value is not False:875self.col_lower_bound[index] = value876else:877return self.col_lower_bound[index]878879880