From 144a970ae97b209acccf6d38de540401c8c8a587 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Tue, 5 Apr 2016 11:54:36 -0700 Subject: [PATCH] PPLBackend: Add support for integer variables --- src/sage/numerical/backends/ppl_backend.pyx | 101 ++++++++++++++++---- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/src/sage/numerical/backends/ppl_backend.pyx b/src/sage/numerical/backends/ppl_backend.pyx index e633fd31f21..2b91ef80ce1 100644 --- a/src/sage/numerical/backends/ppl_backend.pyx +++ b/src/sage/numerical/backends/ppl_backend.pyx @@ -20,7 +20,7 @@ AUTHORS: #***************************************************************************** from sage.numerical.mip import MIPSolverException -from sage.libs.ppl import MIP_Problem, Variable, Linear_Expression, Constraint, Generator +from sage.libs.ppl import MIP_Problem, Variable, Variables_Set, Linear_Expression, Constraint, Generator from sage.rings.integer cimport Integer from sage.rings.rational cimport Rational @@ -36,6 +36,8 @@ cdef class PPLBackend(GenericBackend): cdef list col_name_var cdef int is_maximize cdef str name + cdef object integer_variables + # Common denominator for objective function in self.mip (not for the constant term) cdef Integer obj_denominator @@ -59,6 +61,7 @@ cdef class PPLBackend(GenericBackend): self.name = '' self.obj_constant_term = Rational(0) self.obj_denominator = Integer(1) + self.integer_variables = set() if maximization: self.set_sense(+1) @@ -94,6 +97,13 @@ cdef class PPLBackend(GenericBackend): self.mip.add_space_dimensions_and_embed(len(self.objective_function)) + # Integrality + + ivar = Variables_Set() + for i in self.integer_variables: + ivar.insert(Variable(i)) + self.mip.add_to_integer_space_dimensions(ivar) + # Objective function mip_obj = Linear_Expression(0) denom = Integer(1) @@ -133,7 +143,7 @@ cdef class PPLBackend(GenericBackend): else: self.mip.set_optimization_mode('minimization') - cpdef int add_variable(self, lower_bound=0, upper_bound=None, binary=False, continuous=True, integer=False, obj=0, name=None) except -1: + cpdef int add_variable(self, lower_bound=0, upper_bound=None, binary=False, continuous=False, integer=False, obj=0, name=None) except -1: """ Add a variable. @@ -179,19 +189,28 @@ cdef class PPLBackend(GenericBackend): sage: p.objective_coefficient(2) 2/3 sage: p.add_variable(integer=True) - Traceback (most recent call last): - ... - NotImplementedError: The PPL backend in Sage only supports continuous variables + 3 """ - if binary or integer: - raise NotImplementedError("The PPL backend in Sage only supports continuous variables") + cdef int vtype = int(bool(binary)) + int(bool(continuous)) + int(bool(integer)) + if vtype == 0: + continuous = True + elif vtype != 1: + raise ValueError("Exactly one parameter of 'binary', 'integer' and 'continuous' must be 'True'.") + for i in range(len(self.Matrix)): self.Matrix[i].append(0) self.col_lower_bound.append(lower_bound) self.col_upper_bound.append(upper_bound) self.objective_function.append(obj) self.col_name_var.append(name) - return len(self.objective_function) - 1 + + n = len(self.objective_function) - 1 + if binary: + self.set_variable_type(n,0) + elif integer: + self.set_variable_type(n,1) + + return n cpdef int add_variables(self, int n, lower_bound=0, upper_bound=None, binary=False, continuous=True, integer=False, obj=0, names=None) except -1: """ @@ -264,20 +283,54 @@ cdef class PPLBackend(GenericBackend): """ Set the type of a variable. + INPUT: + + - ``variable`` (integer) -- the variable's id + + - ``vtype`` (integer) : + + * 1 Integer + * 0 Binary + * -1 Continuous + EXAMPLE:: sage: from sage.numerical.backends.generic_backend import get_solver sage: p = get_solver(solver = "PPL") sage: p.add_variables(5) 4 + sage: p.set_variable_type(0,1) + sage: p.is_variable_integer(0) + True + sage: p.set_variable_type(3,0) + sage: p.is_variable_integer(3) or p.is_variable_binary(3) + True + sage: p.col_bounds(3) # tol 1e-6 + (0, 1) sage: p.set_variable_type(3, -1) + sage: p.is_variable_continuous(3) + True + + TESTS: + + Test that an exception is raised when an invalid type is passed:: + sage: p.set_variable_type(3, -2) Traceback (most recent call last): ... - Exception: ... + ValueError: ... """ - if vtype != -1: - raise Exception('This backend does not handle integer variables ! Read the doc !') + if vtype == -1: + if variable in self.integer_variables: + self.integer_variables.remove(variable) + elif vtype == 0: + self.integer_variables.add(variable) + self.variable_lower_bound(variable, 0) + self.variable_upper_bound(variable, 1) + elif vtype == 1: + self.integer_variables.add(variable) + else: + raise ValueError("Invalid variable type: {}".format(vtype)) cpdef set_sense(self, int sense): """ @@ -524,6 +577,7 @@ cdef class PPLBackend(GenericBackend): self.row_name_var.append(None) cpdef int solve(self) except -1: + # integer example copied from cplex_backend.pyx """ Solve the problem. @@ -531,9 +585,11 @@ cdef class PPLBackend(GenericBackend): This method raises ``MIPSolverException`` exceptions when the solution can not be computed for any reason (none - exists, or the LP solver was not able to find it, etc...) + exists, or the solver was not able to find it, etc...) - EXAMPLE:: + EXAMPLES: + + A linear optimization problem:: sage: from sage.numerical.backends.generic_backend import get_solver sage: p = get_solver(solver = "PPL") @@ -541,11 +597,24 @@ cdef class PPLBackend(GenericBackend): sage: p.add_col(range(5), range(5)) sage: p.solve() 0 + + An unbounded problem:: + sage: p.objective_coefficient(0,1) sage: p.solve() Traceback (most recent call last): ... MIPSolverException: ... + + An integer optimization problem:: + + sage: p = MixedIntegerLinearProgram(solver='PPL') + sage: x = p.new_variable(integer=True, nonnegative=True) + sage: p.add_constraint(2*x[0] + 3*x[1], max = 6) + sage: p.add_constraint(3*x[0] + 2*x[1], max = 6) + sage: p.set_objective(x[0] + x[1] + 7) + sage: p.solve() + 9 """ self.init_mip() @@ -809,7 +878,7 @@ cdef class PPLBackend(GenericBackend): sage: p.is_variable_binary(0) False """ - return False + return index in self.integer_variables and self.col_bounds(index) == (0, 1) cpdef bint is_variable_integer(self, int index): """ @@ -830,7 +899,7 @@ cdef class PPLBackend(GenericBackend): sage: p.is_variable_integer(0) False """ - return False + return index in self.integer_variables and self.col_bounds(index) != (0, 1) cpdef bint is_variable_continuous(self, int index): """ @@ -851,7 +920,7 @@ cdef class PPLBackend(GenericBackend): sage: p.is_variable_continuous(0) True """ - return True + return index not in self.integer_variables cpdef row_name(self, int index): """