/* * Copyright 2010 - 2011, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Clemens Zeidler */ #include "ActiveSetSolver.h" #include #include #include "LayoutOptimizer.h" // #define DEBUG_ACTIVE_SOLVER #ifdef DEBUG_ACTIVE_SOLVER #define TRACE(x...) printf(x) #else #define TRACE(x...) /* nothing */ #endif using namespace LinearProgramming; using namespace std; EquationSystem::EquationSystem(int32 rows, int32 columns) : fRows(rows), fColumns(columns) { fMatrix = allocate_matrix(fRows, fColumns); fB = new double[fColumns]; // better init all values to prevent side cases where not all variables // needed to solve the problem, coping theses values to the results could // cause problems for (int i = 0; i < fColumns; i++) fB[i] = 0; zero_matrix(fMatrix, fRows, fColumns); fRowIndices = new int32[fRows]; fColumnIndices = new int32[fColumns]; for (int i = 0; i < fRows; i++) fRowIndices[i] = i; for (int i = 0; i < fColumns; i++) fColumnIndices[i] = i; } EquationSystem::~EquationSystem() { free_matrix(fMatrix); delete[] fB; delete[] fRowIndices; delete[] fColumnIndices; } void EquationSystem::SetRows(int32 rows) { fRows = rows; } int32 EquationSystem::Rows() { return fRows; } int32 EquationSystem::Columns() { return fColumns; } bool EquationSystem::InRange(int32 row, int32 column) { if (row < 0 || row >= fRows) return false; if (column < 0 || column >= fColumns) return false; return true; } double& EquationSystem::A(int32 row, int32 column) { return fMatrix[fRowIndices[row]][fColumnIndices[column]]; } double& EquationSystem::B(int32 row) { return fB[row]; } void EquationSystem::Results(double* results, int32 size) { for (int i = 0; i < size; i++) results[i] = 0; for (int i = 0; i < fColumns; i++) { int32 index = fColumnIndices[i]; if (index < fRows) results[index] = fB[i]; } } void EquationSystem::SwapColumn(int32 i, int32 j) { swap(fColumnIndices[i], fColumnIndices[j]); } void EquationSystem::SwapRow(int32 i, int32 j) { swap(fRowIndices[i], fRowIndices[j]); swap(fB[i], fB[j]); } bool EquationSystem::GaussianElimination() { // basic solve for (int i = 0; i < fRows; i++) { // find none zero element int swapRow = -1; for (int r = i; r < fRows; r++) { double& value = fMatrix[fRowIndices[r]][fColumnIndices[i]]; if (fuzzy_equals(value, 0)) continue; swapRow = r; break; } if (swapRow == -1) { int swapColumn = -1; for (int c = i + 1; c < fColumns; c++) { double& value = fMatrix[fRowIndices[i]][fColumnIndices[c]]; if (fuzzy_equals(value, 0)) continue; swapRow = i; swapColumn = c; break; } if (swapColumn == -1) { TRACE("can't solve column %i\n", i); return false; } SwapColumn(i, swapColumn); } if (i != swapRow) SwapRow(i, swapRow); // normalize _EliminateColumn(i, i + 1, fRows - 1); } return true; } bool EquationSystem::GaussJordan() { if (!GaussianElimination()) return false; for (int32 i = fRows - 1; i >= 0; i--) _EliminateColumn(i, 0, i - 1); return true; } void EquationSystem::GaussJordan(int32 i) { _EliminateColumn(i, 0, fRows - 1); } void EquationSystem::RemoveLinearlyDependentRows() { double** temp = allocate_matrix(fRows, fColumns); bool independentRows[fRows]; // copy to temp copy_matrix(fMatrix, temp, fRows, fColumns); int nIndependent = compute_dependencies(temp, fRows, fColumns, independentRows); if (nIndependent == fRows) return; // remove the rows for (int i = 0; i < fRows; i++) { if (!independentRows[i]) { int lastDepRow = -1; for (int d = fRows - 1; d > i; d--) { if (independentRows[d]) { lastDepRow = d; break; } } if (lastDepRow < 0) break; SwapRow(i, lastDepRow); fRows--; } } fRows = nIndependent; free_matrix(temp); } void EquationSystem::RemoveUnusedVariables() { for (int c = 0; c < fColumns; c++) { bool used = false; for (int r = 0; r < fRows; r++) { if (!fuzzy_equals(fMatrix[r][fColumnIndices[c]], 0)) { used = true; break; } } if (used) continue; //MoveColumnRight(c, fColumns - 1); SwapColumn(c, fColumns - 1); fColumns--; c--; } } void EquationSystem::MoveColumnRight(int32 i, int32 target) { int32 index = fColumnIndices[i]; for (int c = i; c < target; c++) fColumnIndices[c] = fColumnIndices[c + 1]; fColumnIndices[target] = index; } void EquationSystem::Print() { for (int m = 0; m < fRows; m++) { for (int n = 0; n < fColumns; n++) printf("%.1f ", fMatrix[fRowIndices[m]][fColumnIndices[n]]); printf("= %.1f\n", fB[m]); } } void EquationSystem::_EliminateColumn(int32 column, int32 startRow, int32 endRow) { double value = fMatrix[fRowIndices[column]][fColumnIndices[column]]; if (value != 1.) { for (int j = column; j < fColumns; j++) fMatrix[fRowIndices[column]][fColumnIndices[j]] /= value; fB[column] /= value; } for (int r = startRow; r < endRow + 1; r++) { if (r == column) continue; double q = -fMatrix[fRowIndices[r]][fColumnIndices[column]]; // don't need to do anything, since matrix is typically sparse this // should save some work if (fuzzy_equals(q, 0)) continue; for (int c = column; c < fColumns; c++) fMatrix[fRowIndices[r]][fColumnIndices[c]] += fMatrix[fRowIndices[column]][fColumnIndices[c]] * q; fB[r] += fB[column] * q; } } namespace { Constraint* AddMinConstraint(LinearSpec* spec, Variable* var) { return spec->AddConstraint(1, var, kEQ, 0, 5, 5); } Constraint* AddMaxConstraint(LinearSpec* spec, Variable* var) { static const double kHugeValue = 32000; return spec->AddConstraint(1, var, kEQ, kHugeValue, 5, 5); } }; ActiveSetSolver::ActiveSetSolver(LinearSpec* linearSpec) : SolverInterface(linearSpec), fVariables(linearSpec->UsedVariables()), fConstraints(linearSpec->Constraints()) { } ActiveSetSolver::~ActiveSetSolver() { } /* Using algorithm found in: Solving Inequalities and Proving Farkas's Lemma Made Easy David Avis and Bohdan Kaluzny The American Mathematical Monthly Vol. 111, No. 2 (Feb., 2004), pp. 152-157 */ static bool solve(EquationSystem& system) { // basic solve if (!system.GaussJordan()) return false; bool done = false; while (!done) { double smallestB = HUGE_VALF; int smallestBRow = -1; for (int row = 0; row < system.Rows(); row++) { if (system.B(row) > 0 || fuzzy_equals(system.B(row), 0)) continue; double bValue = fabs(system.B(row)); if (bValue < smallestB) { smallestB = bValue; smallestBRow = row; } } if (smallestBRow == -1) { done = true; break; } int negValueCol = -1; for (int col = system.Rows(); col < system.Columns(); col++) { double value = system.A(smallestBRow, col); if (value > 0 || fuzzy_equals(value, 0)) continue; negValueCol = col; break; } if (negValueCol == -1) { TRACE("can't solve\n"); return false; } system.SwapColumn(smallestBRow, negValueCol); // eliminate system.GaussJordan(smallestBRow); } return true; } static bool is_soft_inequality(Constraint* constraint) { if (constraint->PenaltyNeg() <= 0. && constraint->PenaltyPos() <= 0.) return false; if (constraint->Op() != kEQ) return true; return false; } class SoftInequalityAdder { public: SoftInequalityAdder(LinearSpec* linSpec, ConstraintList& allConstraints) : fLinearSpec(linSpec) { for (int32 c = 0; c < allConstraints.CountItems(); c++) { Constraint* constraint = allConstraints.ItemAt(c); if (!is_soft_inequality(constraint)) continue; Variable* variable = fLinearSpec->AddVariable(); variable->SetRange(0, 20000); Constraint* helperConst = fLinearSpec->AddConstraint(1, variable, kEQ, 0, constraint->PenaltyNeg(), constraint->PenaltyPos()); fInequalitySoftConstraints.AddItem(helperConst); double coeff = -1; if (constraint->Op() == kGE) coeff = 1; Constraint* modifiedConstraint = new Constraint(constraint); allConstraints.AddItem(modifiedConstraint, c); allConstraints.RemoveItemAt(c + 1); fModifiedIneqConstraints.AddItem(modifiedConstraint); modifiedConstraint->LeftSide()->AddItem( new Summand(coeff, variable)); } for (int32 c = 0; c < fInequalitySoftConstraints.CountItems(); c++) allConstraints.AddItem(fInequalitySoftConstraints.ItemAt(c)); } ~SoftInequalityAdder() { for (int32 c = 0; c < fModifiedIneqConstraints.CountItems(); c++) delete fModifiedIneqConstraints.ItemAt(c); for (int32 c = 0; c < fInequalitySoftConstraints.CountItems(); c++) { Constraint* con = fInequalitySoftConstraints.ItemAt(c); fLinearSpec->RemoveVariable(con->LeftSide()->ItemAt(0)->Var()); // this also deletes the constraint } } private: LinearSpec* fLinearSpec; ConstraintList fModifiedIneqConstraints; ConstraintList fInequalitySoftConstraints; }; ResultType ActiveSetSolver::Solve() { // make a copy of the original constraints and create soft inequality // constraints ConstraintList allConstraints(fConstraints); SoftInequalityAdder adder(fLinearSpec, allConstraints); int32 nConstraints = allConstraints.CountItems(); int32 nVariables = fVariables.CountItems(); if (nVariables > nConstraints) { TRACE("More variables then constraints! vars: %i, constraints: %i\n", (int)nVariables, (int)nConstraints); return kInfeasible; } /* First find an initial solution and then optimize it using the active set method. */ EquationSystem system(nConstraints, nVariables + nConstraints); int32 slackIndex = nVariables; // setup constraint matrix and add slack variables if necessary int32 rowIndex = 0; for (int32 c = 0; c < nConstraints; c++) { Constraint* constraint = allConstraints.ItemAt(c); if (is_soft(constraint)) continue; SummandList* leftSide = constraint->LeftSide(); system.B(rowIndex) = constraint->RightSide(); for (int32 sIndex = 0; sIndex < leftSide->CountItems(); sIndex++ ) { Summand* summand = leftSide->ItemAt(sIndex); double coefficient = summand->Coeff(); int32 columnIndex = summand->VariableIndex(); system.A(rowIndex, columnIndex) = coefficient; } if (constraint->Op() == kLE) { system.A(rowIndex, slackIndex) = 1; slackIndex++; } else if (constraint->Op() == kGE) { system.A(rowIndex, slackIndex) = -1; slackIndex++; } rowIndex++; } system.SetRows(rowIndex); system.RemoveLinearlyDependentRows(); system.RemoveUnusedVariables(); if (!solve(system)) return kInfeasible; double results[nVariables + nConstraints]; system.Results(results, nVariables + nConstraints); TRACE("base system solved\n"); LayoutOptimizer optimizer(allConstraints, nVariables); if (!optimizer.Solve(results)) return kInfeasible; // back to the variables for (int32 i = 0; i < nVariables; i++) fVariables.ItemAt(i)->SetValue(results[i]); for (int32 i = 0; i < nVariables; i++) TRACE("var %f\n", results[i]); return kOptimal; } bool ActiveSetSolver::VariableAdded(Variable* variable) { if (fVariableGEConstraints.AddItem(NULL) == false) return false; if (fVariableLEConstraints.AddItem(NULL) == false) { // clean up int32 count = fVariableGEConstraints.CountItems(); fVariableGEConstraints.RemoveItemAt(count - 1); return false; } return true; } bool ActiveSetSolver::VariableRemoved(Variable* variable) { fVariableGEConstraints.RemoveItemAt(variable->GlobalIndex()); fVariableLEConstraints.RemoveItemAt(variable->GlobalIndex()); return true; } bool ActiveSetSolver::VariableRangeChanged(Variable* variable) { double min = variable->Min(); double max = variable->Max(); int32 variableIndex = variable->GlobalIndex(); Constraint* constraintGE = fVariableGEConstraints.ItemAt(variableIndex); Constraint* constraintLE = fVariableLEConstraints.ItemAt(variableIndex); if (constraintGE == NULL && min > -20000) { constraintGE = fLinearSpec->AddConstraint(1, variable, kGE, 0); if (constraintGE == NULL) return false; constraintGE->SetLabel("Var Min"); fVariableGEConstraints.RemoveItemAt(variableIndex); fVariableGEConstraints.AddItem(constraintGE, variableIndex); } if (constraintLE == NULL && max < 20000) { constraintLE = fLinearSpec->AddConstraint(1, variable, kLE, 20000); if (constraintLE == NULL) return false; constraintLE->SetLabel("Var Max"); fVariableLEConstraints.RemoveItemAt(variableIndex); fVariableLEConstraints.AddItem(constraintLE, variableIndex); } if (constraintGE) constraintGE->SetRightSide(min); if (constraintLE) constraintLE->SetRightSide(max); return true; } bool ActiveSetSolver::ConstraintAdded(Constraint* constraint) { return true; } bool ActiveSetSolver::ConstraintRemoved(Constraint* constraint) { return true; } bool ActiveSetSolver::LeftSideChanged(Constraint* constraint) { return true; } bool ActiveSetSolver::RightSideChanged(Constraint* constraint) { return true; } bool ActiveSetSolver::OperatorChanged(Constraint* constraint) { return true; } bool ActiveSetSolver::PenaltiesChanged(Constraint* constraint) { return true; } bool ActiveSetSolver::SaveModel(const char* fileName) { return false; } ResultType ActiveSetSolver::FindMaxs(const VariableList* variables) { return _FindWithConstraintsNoSoft(variables, AddMaxConstraint); } ResultType ActiveSetSolver::FindMins(const VariableList* variables) { return _FindWithConstraintsNoSoft(variables, AddMinConstraint); } void ActiveSetSolver::GetRangeConstraints(const Variable* var, const Constraint** _min, const Constraint** _max) const { int32 variableIndex = var->GlobalIndex(); if (_min) *_min = fVariableGEConstraints.ItemAt(variableIndex); if (_max) *_max = fVariableLEConstraints.ItemAt(variableIndex); } void ActiveSetSolver::_RemoveSoftConstraint(ConstraintList& list) { ConstraintList allConstraints = fLinearSpec->Constraints(); for (int i = 0; i < allConstraints.CountItems(); i++) { Constraint* constraint = allConstraints.ItemAt(i); // soft eq an ineq constraint? if (constraint->PenaltyNeg() <= 0. && constraint->PenaltyPos() <= 0.) continue; if (RemoveConstraint(constraint, false, false) == true) list.AddItem(constraint); } } void ActiveSetSolver::_AddSoftConstraint(const ConstraintList& list) { for (int i = 0; i < list.CountItems(); i++) { Constraint* constraint = list.ItemAt(i); // at least don't leak it if (AddConstraint(constraint, false) == false) delete constraint; } } ResultType ActiveSetSolver::_FindWithConstraintsNoSoft(const VariableList* variables, AddConstraintFunc constraintFunc) { ConstraintList softConstraints; _RemoveSoftConstraint(softConstraints); ConstraintList constraints; for (int32 i = 0; i < variables->CountItems(); i++) constraints.AddItem(constraintFunc(fLinearSpec, variables->ItemAt(i))); ResultType result = Solve(); for (int32 i = 0; i < constraints.CountItems(); i++) fLinearSpec->RemoveConstraint(constraints.ItemAt(i)); _AddSoftConstraint(softConstraints); if (result != kOptimal) TRACE("Could not solve the layout specification (%d). ", result); return result; }