//----------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All Rights Reserved. // //----------------------------------------------------------------------------- namespace Microsoft.AbstractInterpretationFramework { using System.Collections; using System; using Microsoft.Contracts; using Microsoft.Basetypes; using IMutableSet = Microsoft.Boogie.Set; using HashSet = Microsoft.Boogie.Set; /// /// Used by LinearConstraintSystem.GenerateFrameFromConstraints. /// public class SimplexTableau { readonly int rows; readonly int columns; readonly Rational[,]! m; readonly int numInitialVars; readonly int numSlackVars; readonly int rhsColumn; readonly ArrayList /*IVariable!*/! dims; readonly int[]! basisColumns; readonly int[]! inBasis; bool constructionDone = false; void CheckInvariant() { assert(rows == m.GetLength(0)); assert(1 <= columns && columns == m.GetLength(1)); assert(0 <= numInitialVars); assert(0 <= numSlackVars && numSlackVars <= rows); assert(numInitialVars + numSlackVars + 1 == columns); assert(rhsColumn == columns - 1); assert(dims.Count == numInitialVars); assert(basisColumns.Length == rows); assert(inBasis.Length == numInitialVars + numSlackVars); bool[] b = new bool[numInitialVars + numSlackVars]; int numColumnsInBasis = 0; int numUninitializedRowInfo = 0; for (int i = 0; i < rows; i++) { int c = basisColumns[i]; if (c == rhsColumn) { // all coefficients in this row are 0 (but the right-hand side may be non-0) for (int j = 0; j < rhsColumn; j++) { assert m[i,j].IsZero; } numColumnsInBasis++; } else if (c == -1) { assert(!constructionDone); numUninitializedRowInfo++; } else { // basis column is a column assert(0 <= c && c < numInitialVars + numSlackVars); // basis column is unique assert(!b[c]); b[c] = true; // column is marked as being in basis assert(inBasis[c] == i); // basis column really is a basis column for (int j = 0; j < rows; j++) { if (j == i) { assert m[j,c].HasValue(1);// == (Rational)new Rational(1)); } else { assert m[j,c].IsZero; } } } } // no other columns are marked as being in basis foreach (int i in inBasis) { if (0 <= i) { assert(i < rows); numColumnsInBasis++; } else { assert(i == -1); } } assert(rows - numUninitializedRowInfo <= numColumnsInBasis && numColumnsInBasis <= rows); assert(!constructionDone || numUninitializedRowInfo == 0); } /// /// Constructs a matrix that represents the constraints "constraints", adding slack /// variables for the inequalities among "constraints". Puts the matrix in canonical /// form. /// /// [NotDelayed] public SimplexTableau(ArrayList /*LinearConstraint*/! constraints) { #if DEBUG_PRINT Console.WriteLine("DEBUG: SimplexTableau constructor called with:"); foreach (LinearConstraint lc in constraints) { Console.WriteLine(" {0}", lc); } #endif // Note: This implementation is not particularly efficient, but it'll do for now. ArrayList dims = this.dims = new ArrayList /*IVariable!*/ (); int slacks = 0; foreach (LinearConstraint! cc in constraints) { foreach (IVariable! dim in cc.coefficients.Keys) { if (!dims.Contains(dim)) { dims.Add(dim); } } if (cc.Relation == LinearConstraint.ConstraintRelation.LE) { slacks++; } } int numInitialVars = this.numInitialVars = dims.Count; int numSlackVars = this.numSlackVars = slacks; int rows = this.rows = constraints.Count; int columns = this.columns = numInitialVars + numSlackVars + 1; this.m = new Rational[rows, columns]; this.rhsColumn = columns-1; this.basisColumns = new int[rows]; this.inBasis = new int[columns-1]; base(); for (int i = 0; i < inBasis.Length; i++) { inBasis[i] = -1; } // Fill in the matrix int r = 0; int iSlack = 0; foreach (LinearConstraint! cc in constraints) { for (int i = 0; i < dims.Count; i++) { m[r,i] = cc[(IVariable!)dims[i]]; } if (cc.Relation == LinearConstraint.ConstraintRelation.LE) { m[r, numInitialVars + iSlack] = Rational.ONE; basisColumns[r] = numInitialVars + iSlack; inBasis[numInitialVars + iSlack] = r; iSlack++; } else { basisColumns[r] = -1; // special value to communicate to Pivot that basis column i hasn't been set up yet } m[r,rhsColumn] = cc.rhs; r++; } assert(r == constraints.Count); assert(iSlack == numSlackVars); #if DEBUG_PRINT Console.WriteLine("DEBUG: Intermediate tableau state in SimplexTableau constructor:"); Dump(); #endif // Go through the rows with uninitialized basis columns. These correspond to equality constraints. // For each one, find an initial variable (non-slack variable) whose column we can make the basis // column of the row. for (int i = 0; i < rows; i++) { if (basisColumns[i] != -1) { continue; } // Find a non-0 column in row i that we can make a basis column. Since rows corresponding // to equality constraints don't have slack variables and since the pivot operations performed // by iterations of this loop don't introduce any non-0 coefficients in the slack-variable // columns of these rows, we only need to look through the columns corresponding to initial // variables. for (int j = 0; j < numInitialVars; j++) { if (m[i,j].IsNonZero) { #if DEBUG_PRINT Console.WriteLine("-- About to Pivot({0},{1})", i, j); #endif assert(inBasis[j] == -1); Pivot(i,j); #if DEBUG_PRINT Console.WriteLine("Tableau after Pivot:"); Dump(); #endif goto SET_UP_NEXT_INBASIS_COLUMN; } } // Check the assertion in the comment above, that is, that columns corresponding to slack variables // are 0 in this row. for (int j = numInitialVars; j < rhsColumn; j++) { assert m[i,j].IsZero; } // There is no column in this row that we can put into basis. basisColumns[i] = rhsColumn; SET_UP_NEXT_INBASIS_COLUMN: {} } constructionDone = true; CheckInvariant(); } public IMutableSet! /*IVariable!*/ GetDimensions() { HashSet /*IVariable!*/ z = new HashSet /*IVariable!*/ (); foreach (IVariable! dim in dims) { z.Add(dim); } return z; } public Rational this [int r, int c] { get { return m[r,c]; } set { m[r,c] = value; } } /// /// Applies the Pivot Operation on row "r" and column "c". /// /// This method can be called when !constructionDone, that is, at a time when not all basis /// columns have been set up (indicated by -1 in basisColumns). This method helps set up /// those basis columns. /// /// The return value is an undo record that can be used with UnPivot. /// /// /// public Rational[]! Pivot(int r, int c) { assert(0 <= r && r < rows); assert(0 <= c && c < columns-1); assert(m[r,c].IsNonZero); assert(inBasis[c] == -1); // follows from invariant and m[r,c] != 0 assert(basisColumns[r] != rhsColumn); // follows from invariant and m[r,c] != 0 Rational[] undo = new Rational[rows+1]; for (int i = 0; i < rows; i++) { undo[i] = m[i,c]; } // scale the pivot row Rational q = m[r,c]; if (q != Rational.ONE) { for (int j = 0; j < columns; j++) { m[r,j] /= q; } } // subtract a multiple of the pivot row from all other rows for (int i = 0; i < rows; i++) { if (i != r) { q = m[i,c]; if (q.IsNonZero) { for (int j = 0; j < columns; j++) { m[i,j] -= q * m[r,j]; } } } } // update basis information int prevCol = basisColumns[r]; undo[rows] = Rational.FromInt(prevCol); basisColumns[r] = c; if (prevCol != -1) { inBasis[prevCol] = -1; } inBasis[c] = r; return undo; } /// /// If the last operation applied to the tableau was: /// undo = Pivot(i,j); /// then UnPivot(i, j, undo) undoes the pivot operation. /// Note: This operation is not supported for any call to Pivot before constructionDone /// is set to true. /// /// /// /// void UnPivot(int r, int c, Rational[]! undo) { assert(0 <= r && r < rows); assert(0 <= c && c < columns-1); assert(m[r,c].HasValue(1)); assert(undo.Length == rows+1); // add a multiple of the pivot row to all other rows for (int i = 0; i < rows; i++) { if (i != r) { Rational q = undo[i]; if (q.IsNonZero) { for (int j = 0; j < columns; j++) { m[i,j] += q * m[r,j]; } } } } // scale the pivot row Rational p = undo[r]; for (int j = 0; j < columns; j++) { m[r,j] *= p; } // update basis information int prevCol = undo[rows].AsInteger; assert(prevCol != -1); basisColumns[r] = prevCol; inBasis[c] = -1; inBasis[prevCol] = r; } /// /// Returns true iff the current basis of the system of constraints modeled by the simplex tableau /// is feasible. May have a side effect of performing a number of pivot operations on the tableau, /// but any such pivot operation will be in the columns of slack variables (that is, this routine /// does not change the set of initial-variable columns in basis). /// /// CAVEAT: I have no particular reason to believe that the algorithm used here will terminate. --KRML /// /// public bool IsFeasibleBasis { get { // while there is a slack variable in basis whose row has a negative right-hand side while (true) { bool feasibleBasis = true; for (int c = numInitialVars; c < rhsColumn; c++) { int k = inBasis[c]; if (0 <= k && k < rhsColumn && m[k,rhsColumn].IsNegative) { assert(m[k,c].HasValue(1)); // c is in basis // Try to pivot on a different slack variable in this row for (int i = numInitialVars; i < rhsColumn; i++) { if (m[k,i].IsNegative) { assert(c != i); // c is in basis, so m[k,c]==1, which is not negative Pivot(k, i); #if DEBUG_PRINT Console.WriteLine("Tableau after Pivot operation on ({0},{1}) in IsFeasibleBasis:", k, i); Dump(); #endif assert(inBasis[c] == -1); assert(inBasis[i] == k); assert(m[k,rhsColumn].IsNonNegative); goto START_ANEW; } } feasibleBasis = false; } } return feasibleBasis; START_ANEW: ; } return false; // make compiler shut up } } /// /// Whether or not all initial variables (the non-slack variables) are in basis) /// public bool AllInitialVarsInBasis { get { for (int i = 0; i < numInitialVars; i++) { if (inBasis[i] == -1) { return false; } } return true; } } /// /// Adds as many initial variables as possible to the basis. /// /// public void AddInitialVarsToBasis() { // while there exists an initial variable not in the basis and not satisfying // condition 3.4.2.2 in Cousot and Halbwachs, perform a pivot operation while (true) { for (int i = 0; i < numInitialVars; i++) { if (inBasis[i] == -1) { // initial variable i is not in the basis for (int j = 0; j < rows; j++) { if (m[j,i].IsNonZero) { int k = basisColumns[j]; if (numInitialVars <= k && k < rhsColumn) { // slack variable k is in basis for row j Pivot(j, i); assert(inBasis[k] == -1); assert(inBasis[i] == j && basisColumns[j] == i); goto START_ANEW; } } } } } // No more initial variables can be moved into basis. return; START_ANEW: {} } } /// /// Adds to "lines" the lines implied by initial-variable columns not in basis /// (see section 3.4.2 of Cousot and Halbwachs), and adds to "constraints" the /// constraints to exclude those lines (see step 4.2 of section 3.4.3 of /// Cousot and Halbwachs). /// /// /// public void ProduceLines(ArrayList /*FrameElement*/! lines, ArrayList /*LinearConstraint*/! constraints) { // for every initial variable not in basis for (int i0 = 0; i0 < numInitialVars; i0++) { if (inBasis[i0] == -1) { FrameElement fe = new FrameElement(); LinearConstraint lc = new LinearConstraint(LinearConstraint.ConstraintRelation.EQ); for (int i = 0; i < numInitialVars; i++) { if (i == i0) { fe.AddCoordinate((IVariable!)dims[i], Rational.ONE); lc.SetCoefficient((IVariable!)dims[i], Rational.ONE); } else if (inBasis[i] != -1) { // i is a basis column assert(m[inBasis[i],i].HasValue(1)); Rational val = -m[inBasis[i],i0]; fe.AddCoordinate((IVariable!)dims[i], val); lc.SetCoefficient((IVariable!)dims[i], val); } } lines.Add(fe); constraints.Add(lc); } } } /// /// From a feasible point where all initial variables are in the basis, traverses /// all feasible bases containing all initial variables. For each such basis, adds /// the vertices to "vertices" and adds to "rays" the extreme rays. See step 4.2 /// in section 3.4.3 of Cousot and Halbwachs. /// A more efficient algorithm is found in the paper "An algorithm for /// determining all extreme points of a convex polytope" by N. E. Dyer and L. G. Proll, /// Mathematical Programming, 12, 1977. /// Assumes that the tableau is in a state where all initial variables are in the basis. /// This method has no net effect on the tableau. /// Note: Duplicate vertices and rays may be added. /// /// /// public void TraverseVertices(ArrayList! /*FrameElement*/ vertices, ArrayList! /*FrameElement*/ rays) { ArrayList /*bool[]*/ basesSeenSoFar = new ArrayList /*bool[]*/ (); TraverseBases(basesSeenSoFar, vertices, rays); } /// /// Worker method of TraverseVertices. /// This method has no net effect on the tableau. /// /// /// /// void TraverseBases(ArrayList /*bool[]*/! basesSeenSoFar, ArrayList /*FrameElement*/! vertices, ArrayList /*FrameElement*/! rays) { CheckInvariant(); bool[] thisBasis = new bool[numSlackVars]; for (int i = numInitialVars; i < rhsColumn; i++) { if (inBasis[i] != -1) { thisBasis[i-numInitialVars] = true; } } foreach (bool[]! basis in basesSeenSoFar) { assert(basis.Length == numSlackVars); for (int i = 0; i < numSlackVars; i++) { if (basis[i] != thisBasis[i]) { goto COMPARE_WITH_NEXT_BASIS; } } // thisBasis and basis are the same--that is, basisColumns has been visited before--so // we don't traverse anything from here return; COMPARE_WITH_NEXT_BASIS: {} } // basisColumns has not been seen before; record thisBasis and continue with the traversal here basesSeenSoFar.Add(thisBasis); #if DEBUG_PRINT Console.Write("TraverseBases, new basis: "); foreach (bool t in thisBasis) { Console.Write("{0}", t ? "*" : "."); } Console.WriteLine(); Dump(); #endif // Add vertex FrameElement v = new FrameElement(); for (int i = 0; i < rows; i++) { int j = basisColumns[i]; if (j < numInitialVars) { v.AddCoordinate((IVariable!)dims[j], m[i,rhsColumn]); } } #if DEBUG_PRINT Console.WriteLine(" Adding vertex: {0}", v); #endif vertices.Add(v); // Add rays. Traverse all columns corresponding to slack variables that // are not in basis (see second bullet of section 3.4.2 of Cousot and Halbwachs). for (int i0 = numInitialVars; i0 < rhsColumn; i0++) { if (inBasis[i0] != -1) { // skip those slack-variable columns that are in basis continue; } // check if slack-variable, non-basis column i corresponds to an extreme ray for (int row = 0; row < rows; row++) { if (m[row,i0].IsPositive) { for (int k = numInitialVars; k < rhsColumn; k++) { if (inBasis[k] != -1 && m[row,k].IsNonZero) { // does not correspond to an extreme ray goto CHECK_NEXT_SLACK_VAR; } } } } // corresponds to an extreme ray FrameElement ray = new FrameElement(); for (int i = 0; i < numInitialVars; i++) { int j0 = inBasis[i]; Rational val = -m[j0,i0]; ray.AddCoordinate((IVariable!)dims[i], val); } #if DEBUG_PRINT Console.WriteLine(" Adding ray: {0}", ray); #endif rays.Add(ray); CHECK_NEXT_SLACK_VAR: {} } // Continue traversal for (int i = numInitialVars; i < rhsColumn; i++) { int j = inBasis[i]; if (j != -1) { // try moving i out of basis and some other slack-variable column into basis for (int k = numInitialVars; k < rhsColumn; k++) { if (inBasis[k] == -1 && m[j,k].IsPositive) { Rational[] undo = Pivot(j, k); // check if the new basis is feasible for (int p = 0; p < rows; p++) { int c = basisColumns[p]; if (numInitialVars <= c && c < rhsColumn && m[p,rhsColumn].IsNegative) { // not feasible goto AFTER_TRAVERSE; } } TraverseBases(basesSeenSoFar, vertices, rays); AFTER_TRAVERSE: UnPivot(j, k, undo); } } } } } public void Dump() { // names Console.Write(" "); for (int i = 0; i < numInitialVars; i++) { Console.Write(" {0,4} ", dims[i]); } Console.WriteLine(); // numbers Console.Write(" "); for (int i = 0; i < columns; i++) { if (i == numInitialVars || i == rhsColumn) { Console.Write("|"); } Console.Write(" {0,4}", i); if (i < rhsColumn && inBasis[i] != -1) { Console.Write("* "); assert(basisColumns[inBasis[i]] == i); } else { Console.Write(" "); } } Console.WriteLine(); // line Console.Write(" "); for (int i = 0; i < columns; i++) { if (i == numInitialVars || i == rhsColumn) { Console.Write("+"); } Console.Write("---------"); } Console.WriteLine(); for (int j = 0; j < rows; j++) { Console.Write("{0,4}: ", basisColumns[j]); for (int i = 0; i < columns; i++) { if (i == numInitialVars || i == rhsColumn) { Console.Write("|"); } Console.Write(" {0,4:n1} ", m[j,i]); } Console.WriteLine(); } } } }