public State(LazyState currentState, int removedRowIndex, int removedColumnIndex , LazyState newState) { CurrentState = currentState; RemovedRowIndex = removedRowIndex; RemovedColumnIndex = removedColumnIndex; NewState = newState; }
/// <summary> /// Решает задачу LP алгоритмом Seide /// Для финального вычисления использует Симплекс - метод /// </summary> /// <param name="x">Оптимальная точка</param> /// <param name="value">Оптимальное значение функционала</param> /// <returns></returns> public SimplexResult Solv(out decimal[] x, out decimal value) { // инициализация результата x = null; value = decimal.MinValue; var rowsNumber = A.Length; var a = new decimal[rowsNumber][]; for (var i = 0; i < rowsNumber; ++i) { a[i] = A[i]; } currentState_.SetState(a, b, c); var rnd = new Random(); states_ = new Stack<State>(); //стек состояний, чтобы избавиться от явной рекурсии //есть лишние ограничения, которые нужно исключить for (;;) { #region симуляция рекурсивных вызовов var rows = currentState_.Rows; var columns = currentState_.Columns; if (rows <= columns || rows - columns < 3) { // здесь у нас остались только базисные ограничения (или избыток мал (<3)) // просто решаем задачу симлекс-методом // добавляем вспомогательные переменные, чтобы превратить неравенства в равенства var eq = BuildSimplexProblem(currentState_); Log_.DebugFormat("Solving subproblem of size {0}x{1}", eq.A.Length, eq.c.Length); var result = eq.Solv(); if (result != SimplexResult.Optimal) { while (result == SimplexResult.FunctionalUnbound) { // делать нечего. придется возвращать предыдущие удаленные ограничения поштучно // и решать задачу с ними // если к unbound привело снятие самого первого ограничения, // то нам зверски не повезло // может быть есть какой-то более грамотный путь проверить ограниченность // задачи с данным ограничением, но пока я его не придумал RollbackStates(false); if (states_.Count == 0) { return SimplexResult.FunctionalUnbound; } currentState_ = states_.Pop().CurrentState; rows = currentState_.Rows; columns = currentState_.Columns; eq = BuildSimplexProblem(currentState_); Log_.DebugFormat("Solving subproblem of size {0}x{1}", eq.A.Length, eq.c.Length); result = eq.Solv(); } if (result != SimplexResult.Optimal) { // если случился empty, то нужно возвращаться до состояния перед добавлением очередного ограничения if (result != SimplexResult.Infeasible) { return result; } RollbackStates(true); if (states_.Count == 0) { //увы, исходная задача неразрешима return SimplexResult.Infeasible; } rows = currentState_.Rows; columns = currentState_.Columns; } } if (result == SimplexResult.Optimal) { value = eq.Value; // убираем лишние переменные x = new decimal[columns]; Array.Copy(eq.Solution, x, columns); // возвращаемся на уровень выше } while (states_.Count > 0) { #region возврат вверх по "стеку" var previousState = states_.Pop(); var removedRowIndex = previousState.RemovedRowIndex; var removedColumnIndex = previousState.RemovedColumnIndex; if (removedColumnIndex < 0) //ранее удаляли строку { #region удаляли строку var removedRow = previousState.NewState.RemovedRow; // проверяем, нарушает ли решение удаленное ограничение var val = 0.0m; if (result == SimplexResult.Optimal) { for (var i = 0; i < columns; ++i) { val += removedRow[i] * x[i]; } } if (result != SimplexResult.Optimal || val - previousState.NewState.RemovedB > SimplexProblemBase.Epsilon) { // ограничение нарушено // удаляем одну из переменных из неравенств, превратив удаленное ранее неравенство в равенство // и добавив ограничение на положительность удаленной переменной // (иначе легко огребаем неограниченность функционала) var maxValue = Math.Abs(removedRow[0]); var columnIndexToRemove = 0; for (var i = 1; i < columns; ++i) { var testValue = Math.Abs(removedRow[i]); if (testValue > maxValue) { maxValue = testValue; columnIndexToRemove = i; } } var tempRow = new decimal[columns]; Array.Copy(removedRow, tempRow, columns); removedRow = tempRow; if (maxValue < SimplexProblemBase.Epsilon) { // тут возможны 3 варианта: // 1) result == Success. Значит правая часть < 0 и условие невыполнимо. // система с данным ограничением несовместна. нужно откручивать предыдущее закрепленное ограничение // 2) result != Success и правая часть < 0. условие невыполнимо // нужно откручивать предыдущее закрепленное ограничение // 3) правая часть >= 0. условие выполняется автоматом, // a) result == FunctionalUnbound нужно откручивать до предыдущего снятого ограничения // b) result == Infeasible. Ошибкой уже не считается, т.к. мы уже сняли одно ограничение, // которое возможно ее вызвало. нужно вернуться на предыдущее снятое ограничение result = result == SimplexResult.Optimal || previousState.NewState.RemovedB < 0 ? SimplexResult.Infeasible : SimplexResult.FunctionalUnbound; RollbackStates(result == SimplexResult.Infeasible); if (states_.Count == 0) { return result; // не судьба } rows = currentState_.Rows; columns = currentState_.Columns; continue; // переход на открутку на предыдущий уровень } maxValue = removedRow[columnIndexToRemove]; //теперь уже со знаком var bLeadValue = previousState.NewState.RemovedB / maxValue; if (columns > 1) { for (var j = 0; j < columns; ++j) { if (j == columnIndexToRemove) { continue; } var jValue = removedRow[j]; if (jValue != 0) { jValue /= maxValue; removedRow[j] = jValue; } } var newA = new decimal[rows + 1][]; var newB = new decimal[rows + 1]; for (var i = 0; i < rows; ++i) { var newRow = new decimal[columns - 1]; var row = currentState_.A[i]; var rowLeadValue = row[columnIndexToRemove]; var currentColumn = 0; for (var j = 0; j < columns; ++j) { if (j == columnIndexToRemove) { continue; } var rcValue = row[j]; if (rowLeadValue != 0) { var jValue = removedRow[j]; if (jValue != 0) { rcValue -= jValue * rowLeadValue; } } newRow[currentColumn++] = rcValue; } newA[i] = newRow; var bValue = currentState_.b[i]; if (bLeadValue != 0 && rowLeadValue != 0) { bValue -= bLeadValue * rowLeadValue; } newB[i] = bValue; } var newC = new decimal[columns - 1]; var cLeadValue = currentState_.c[columnIndexToRemove]; var newLastRow = new decimal[columns - 1]; var newColumnIndex = 0; for (var j = 0; j < columns; ++j) { if (j == columnIndexToRemove) { continue; } var cValue = currentState_.c[j]; if (cLeadValue != 0) { var jValue = removedRow[j]; if (jValue != 0) { cValue -= jValue * cLeadValue; } } newC[newColumnIndex] = cValue; // новое ограничение на неотрицательность удаленной переменной newLastRow[newColumnIndex++] = removedRow[j]; } newA[rows] = newLastRow; newB[rows] = bLeadValue; var newState = new LazyState(); newState.SetState(newA, newB, newC); states_.Push(new State(previousState.CurrentState.Clone() , removedRowIndex, columnIndexToRemove , newState.Clone())); currentState_ = newState; break; // переход на внешний уровень for... } // значение считается "в лоб". Это bLeadValue // проверяем на неотрицательность: if (bLeadValue < 0) //система несовместна { // !!! здесь мы сами зафиксировали ограничение, но не добавляли его в стек // поэтому оно уже откатилось. нужно только найти предыдущую точку // где было отброшено ограничение RollbackStates(false); if (states_.Count == 0) { return SimplexResult.Infeasible; // не судьба } result = SimplexResult.FunctionalUnbound; rows = currentState_.Rows; columns = currentState_.Columns; continue; // переход на открутку на предыдущий уровень } result = SimplexResult.Optimal; x = new decimal[1]; x[0] = bLeadValue; value = bLeadValue != 0 ? currentState_.c[0] * bLeadValue : 0; } // else //ограничение не нарушено. просто возвращаемся на предыдущий уровень #endregion удаляли строку } else { #region удаляли переменную // до этого удаляли переменную // вычислим ее, новый вектор x и новое значение функционала var removedRow = previousState.CurrentState.A[removedRowIndex]; var newX = new decimal[columns + 1]; var xRemoved = previousState.CurrentState.b[removedRowIndex]; decimal newVal = 0; var xIndex = 0; for (var i = 0; i < columns + 1; ++i) { if (i == removedColumnIndex) { continue; } var xVal = x[xIndex++]; newX[i] = xVal; xRemoved -= removedRow[i] * xVal; newVal += previousState.CurrentState.c[i] * xVal; } xRemoved /= removedRow[removedColumnIndex]; newX[removedColumnIndex] = xRemoved; newVal += previousState.CurrentState.c[removedColumnIndex] * xRemoved; x = newX; value = newVal; #endregion удаляли переменную } currentState_ = previousState.CurrentState; rows = currentState_.Rows; columns = currentState_.Columns; // возвращаемся на предыдущий уровень #endregion возврат вверх по "стеку" } if (states_.Count == 0) { return result; } // переходим к следующей вложенной итерации } else { // рандомно выбираем ограничение для исключения var rowIndexToDelete = rnd.Next(rows); var oldState = currentState_.Clone(); currentState_.UpdateState(rowIndexToDelete); states_.Push(new State(oldState, rowIndexToDelete, -1 , currentState_.Clone())); } #endregion симуляция рекурсивных вызовов } }
/// <summary>Клонирование</summary> public LazyState Clone() { var state = new LazyState { removedRows_ = removedRows_, removedRowsCount_ = removedRowsCount_, A_ = A_, b_ = b_, c_ = c_, leftRows_ = leftRows_, removedRow_ = removedRow_, removedB_ = removedB_ }; return state; }
/// <summary>Откручивает состояния до ближайшего снятого ограничения</summary> private void RollbackStates(bool resultEmpty) { while (states_.Count > 0) { var st = states_.Peek(); if (resultEmpty) { // нужно выбрать в том числе и ближайшее // закрепленное ограничение if (st.RemovedColumnIndex >= 0) { resultEmpty = false; // после того, как его выбрали, ищем следующее удаленное } } else { // останавливаемся на первом удаленном ограничении if (st.RemovedColumnIndex == -1) { // здесь нужно восстановить текущий стейт по тому состоянию, к "после которого" возвращаемся // ибо при откручивании вверх по переполнению возможно, // что текущий state будет не соответствовать тому, который был после перехода currentState_ = st.NewState; break; } } states_.Pop(); } }
/// <summary>Создает задачу LP для симплекс-метода по заданному состоянию</summary> private SimplexProblem BuildSimplexProblem(LazyState state) { var rows = state.Rows; var columns = state.Columns; var extendedColumns = columns + rows; var A1 = new decimal[rows][]; for (var i = 0; i < rows; ++i) { var srcRow = state.A[i]; var tgtRow = new decimal[extendedColumns]; Array.Copy(srcRow, tgtRow, columns); A1[i] = tgtRow; tgtRow[columns + i] = 1; } var c1 = new decimal[extendedColumns]; Array.Copy(state.c, c1, columns); return new SimplexProblem(A1, state.b, c1); }