/// <inheritdoc /> protected override PointValuePair <T> DoOptimize() { // reset the tableau to indicate a non-feasible solution in case // we do not pass phase 1 successfully if (solutionCallback != null) { solutionCallback.Tableau = null; } var tableau = new SimplexTableau <T, TPolicy>(Function, Constraints, GoalType, IsRestrictedToNonNegative, epsilon, maxUlps); SolvePhase1(tableau); tableau.DropPhase1Objective(); // after phase 1, we are sure to have a feasible solution if (solutionCallback != null) { solutionCallback.Tableau = tableau; } while (!tableau.IsOptimal()) { DoIteration(tableau); } // check that the solution respects the nonNegative restriction in case // the epsilon/cutOff values are too large for the actual linear problem // (e.g. with very small constraint coefficients), the solver might actually // find a non-valid solution (with negative coefficients). PointValuePair <T> solution = tableau.GetSolution(); if (IsRestrictedToNonNegative) { T[] coeff = solution.Point; for (int i = 0; i < coeff.Length; i++) { if (Precision <T, TPolicy> .CompareTo(coeff[i], Policy.Zero(), epsilon) < 0) { throw new NoFeasibleSolutionException(); } } } return(solution); }
/// <summary> /// Checks whether the given column is valid pivot column, i.e. will result /// in a valid pivot row. /// When applying Bland's rule to select the pivot column, it may happen that /// there is no corresponding pivot row. This method will check if the selected /// pivot column will return a valid pivot row. /// </summary> /// <param name="tableau"></param> /// <param name="col"></param> /// <returns></returns> private bool IsValidPivotColumn(SimplexTableau <T, TPolicy> tableau, int col) { for (int i = tableau.NumObjectiveFunctions; i < tableau.Height; i++) { T entry = tableau.GetEntry(i, col); // do the same check as in getPivotRow if (Precision <T, TPolicy> .CompareTo(entry, Policy.Zero(), cutOff) > 0) { return(true); } } return(false); }
/// <summary> /// Returns whether the problem is at an optimal state. /// </summary> /// <returns></returns> public bool IsOptimal() { T[] objectiveFunctionRow = tableau.GetRow(0); int end = RhsOffset; for (int i = NumObjectiveFunctions; i < end; i++) { T entry = objectiveFunctionRow[i]; if (Precision <T, TPolicy> .CompareTo(entry, Policy.Zero(), epsilon) < 0) { return(false); } } return(true); }
public static bool Compare(this CompareOperation operation, float a, float b, float precision) { switch (operation) { case CompareOperation.Equal: return(Precision.Equals(a, b, precision)); case CompareOperation.Greater: return(Precision.CompareTo(a, b, precision) > 0); case CompareOperation.GreaterEqual: return(Precision.CompareTo(a, b, precision) >= 0); case CompareOperation.Less: return(Precision.CompareTo(a, b, precision) < 0); case CompareOperation.LessEqual: return(Precision.CompareTo(a, b, precision) <= 0); default: throw new ArgumentOutOfRangeException(nameof(operation), operation, null); } }
/// <summary> /// Removes the phase 1 objective function, positive cost non-artificial variables, /// and the non-basic artificial variables from this tableau. /// </summary> public void DropPhase1Objective() { if (NumObjectiveFunctions == 1) { return; } ISet <int?> columnsToDrop = new SortedSet <int?>(); columnsToDrop.Add(0); // positive cost non-artificial variables for (int i = NumObjectiveFunctions; i < ArtificialVariableOffset; i++) { T entry = GetEntry(0, i); if (Precision <T, TPolicy> .CompareTo(entry, Policy.Zero(), epsilon) > 0) { columnsToDrop.Add(i); } } // non-basic artificial variables for (int i = 0; i < NumArtificialVariables; i++) { int col = i + ArtificialVariableOffset; if (GetBasicRow(col) == null) { columnsToDrop.Add(col); } } T[][] matrix = new T[Height - 1][]; for (var i = 0; i < matrix.Length; i++) { matrix[i] = new T[Width - columnsToDrop.Count]; } for (int i = 1; i < Height; i++) { int col = 0; for (int j = 0; j < Width; j++) { if (!columnsToDrop.Contains(j)) { matrix[i - 1][col++] = GetEntry(i, j); } } } // remove the columns in reverse order so the indices are correct int?[] drop = columnsToDrop.ToArray(); for (int i = drop.Length - 1; i >= 0; i--) { columnLabels.RemoveAt((int)drop[i]); } this.tableau = Matrix <T, TPolicy> .CreateMatrixRowIndexFirst(matrix); this.numArtificialVariables = 0; // need to update the basic variable mappings as row/columns have been dropped InitializeBasicVariables(NumObjectiveFunctions); }
public void CompareTo_float(float a, float b, float precision, int ulp, int expected) { Validate.Value.AreEqual(Precision.CompareTo(a, b, precision), expected); Validate.Value.AreEqual(Precision.CompareTo(a, b, ulp), expected); }
public void CompareTo_double(double a, double b, double precision, int ulp, int expected) { Validate.Value.AreEqual(Precision.CompareTo(a, b, precision), expected); Validate.Value.AreEqual(Precision.CompareTo(a, b, ulp), expected); }
/// <summary> /// Returns the row with the minimum ratio as given by the minimum ratio test (MRT). /// </summary> /// <param name="tableau"></param> /// <param name="col"></param> /// <returns></returns> private int?GetPivotRow(SimplexTableau <T, TPolicy> tableau, int col) { // create a list of all the rows that tie for the lowest score in the minimum ratio test List <int?> minRatioPositions = new List <int?>(); T minRatio = default(T); bool minRationUnassigned = true; for (int i = tableau.NumObjectiveFunctions; i < tableau.Height; i++) { T rhs = tableau.GetEntry(i, tableau.Width - 1); T entry = tableau.GetEntry(i, col); // only consider pivot elements larger than the cutOff threshold // selecting others may lead to degeneracy or numerical instabilities if (Precision <T, TPolicy> .CompareTo(entry, Policy.Zero(), cutOff) > 0) { T ratio = Policy.Abs(Policy.Div(rhs, entry)); // check if the entry is strictly equal to the current min ratio // do not use a ulp/epsilon check int cmp; if (minRationUnassigned) { cmp = -1; } else { cmp = Policy.Compare(ratio, minRatio); } if (cmp == 0) { minRatioPositions.Add(i); } else if (cmp < 0) { minRatio = ratio; minRationUnassigned = false; minRatioPositions.Clear(); minRatioPositions.Add(i); } } } if (minRatioPositions.Count == 0) { return(null); } else if (minRatioPositions.Count > 1) { // there's a degeneracy as indicated by a tie in the minimum ratio test // 1. check if there's an artificial variable that can be forced out of the basis if (tableau.NumArtificialVariables > 0) { foreach (int?row in minRatioPositions) { for (int i = 0; i < tableau.NumArtificialVariables; i++) { int column = i + tableau.ArtificialVariableOffset; T entry = tableau.GetEntry(row.Value, column); if (Precision <T, TPolicy> .Equals(entry, Policy.One(), epsilon) && row.Equals(tableau.GetBasicRow(column))) { return(row); } } } } // 2. apply Bland's rule to prevent cycling: // take the row for which the corresponding basic variable has the smallest index // // see http://www.stanford.edu/class/msande310/blandrule.pdf // see http://en.wikipedia.org/wiki/Bland%27s_rule (not equivalent to the above paper) int?minRow = null; int minIndex = tableau.Width; foreach (int?row in minRatioPositions) { int basicVar = tableau.GetBasicVariable(row.Value); if (basicVar < minIndex) { minIndex = basicVar; minRow = row; } } return(minRow); } return(minRatioPositions[0]); }