# HG changeset patch
# User John Perry <john.perry@usm.edu>
# Date 1314912689 18000
# Node ID c1e11ee479786e1cfdd138935864e6059eb76116
# Parent  561b13b5d2825433807b643d082fa4f1e000231d
Trac 11606: simplify constraints in linear programs

diff -r 561b13b5d282 -r c1e11ee47978 sage/numerical/mip.pxd
--- a/sage/numerical/mip.pxd	Wed Jul 13 19:06:11 2011 +0200
+++ b/sage/numerical/mip.pxd	Thu Sep 01 16:31:29 2011 -0500
@@ -18,6 +18,9 @@
     cdef int __BINARY
     cdef int __REAL
     cdef int __INTEGER
+    cdef int _check_redundant
+    cdef set _constraints
+    cdef _min
     cpdef row(self, int index)
     cpdef row_bounds(self, int index)
     cpdef get_constraints(self)
diff -r 561b13b5d282 -r c1e11ee47978 sage/numerical/mip.pyx
--- a/sage/numerical/mip.pyx	Wed Jul 13 19:06:11 2011 +0200
+++ b/sage/numerical/mip.pyx	Thu Sep 01 16:31:29 2011 -0500
@@ -153,7 +153,7 @@
          4.0
     """
     
-    def __init__(self, solver = None, maximization=True, constraint_generation = False):
+    def __init__(self, solver = None, maximization=True, constraint_generation = False, check_redundant = False):
         r"""
         Constructor for the ``MixedIntegerLinearProgram`` class.
 
@@ -184,6 +184,9 @@
 
         - ``constraint_generation`` -- whether to require the returned solver to
           support constraint generation (excludes Coin). ``False by default``.
+          
+        - ``check_redundant`` -- whether to check that constraints added to the
+          program are redundant with constraints already in the program
 
         .. SEEALSO::
 
@@ -214,6 +217,12 @@
 
         # Associates an index to the variables
         self._variables = {}
+        
+        # Check for redundant constraints
+        self._check_redundant = check_redundant
+        if check_redundant:
+            self._constraints = set()
+            self._min = min
 
     def __repr__(self):
          r"""
@@ -878,7 +887,44 @@
             ...
             ValueError: min and max arguments are required to be numerical
 
-        """
+        Do not add redundant elements (notice only one copy of each constraint is added)::
+        
+            sage: lp = MixedIntegerLinearProgram(check_redundant=True) 
+            sage: for each in xrange(10): lp.add_constraint(lp[0]-lp[1],min=1) 
+            sage: lp.show() 
+            Maximization: 
+            <BLANKLINE> 
+            Constraints: 
+              1.0 <= x_0 -x_1  
+            Variables: 
+              x_0 is a continuous variable (min=0.0, max=+oo) 
+              x_1 is a continuous variable (min=0.0, max=+oo) 
+               
+        We check for constant multiples of constraints as well: 
+         
+            sage: for each in xrange(10): lp.add_constraint(2*lp[0]-2*lp[1],min=2) 
+            sage: lp.show() 
+            Maximization: 
+            <BLANKLINE> 
+            Constraints: 
+              1.0 <= x_0 -x_1  
+            Variables: 
+              x_0 is a continuous variable (min=0.0, max=+oo) 
+              x_1 is a continuous variable (min=0.0, max=+oo) 
+         
+        But if the constant multiple is negative, we should add anyway: 
+         
+              sage: for each in xrange(10): lp.add_constraint(-2*lp[0]+2*lp[1],min=-2) 
+              sage: lp.show() 
+              Maximization: 
+              <BLANKLINE> 
+              Constraints: 
+                1.0 <= x_0 -x_1  
+                x_0 -x_1 <= 1.0
+              Variables: 
+                x_0 is a continuous variable (min=0.0, max=+oo) 
+                x_1 is a continuous variable (min=0.0, max=+oo) 
+                 """
         if linear_function is None or linear_function is 0:
             return None
 
@@ -902,7 +948,24 @@
             indices = []
             values = []
 
-            C = [(v,coeff) for (v,coeff) in f.iteritems() if v != -1]
+            if self._check_redundant:
+              b = self._backend
+              i = self._min([v for (v,coeff) in f.iteritems() if coeff != 0])
+              c = f[i]
+              C = [(v,coeff/c) for (v,coeff) in f.iteritems() if v != -1]
+              if c > 0:
+                min = min/c if min != None else None
+                max = max/c if max != None else None
+              else:
+                tempmin = max/c if max != None else None
+                tempmax = min/c if min != None else None
+                min, max = tempmin, tempmax
+              if (tuple(C),min,max) in self._constraints:
+                return None
+              else:
+                self._constraints.add((tuple(C),min,max))
+            else:
+              C = [(v,coeff) for (v,coeff) in f.iteritems() if v != -1]
 
             if min == None and max == None:
                 raise ValueError("Both max and min are set to None ? Weird!")
