#include #include #include "simplex.h" // a nice tutorial on simplex method: http://math.uww.edu/~mcfarlat/ism.htm CSimplex::CSimplex(): m_numVariables(0),m_numConstraints(0),m_pTableau(0),m_pInitialTableau(0), m_pSolution(0), m_pBasis(0) { } CSimplex::CSimplex(int numVariables, int numConstraints): m_numVariables(0),m_numConstraints(0),m_pTableau(0),m_pInitialTableau(0), m_pSolution(0), m_pBasis(0) { Init(numVariables, numConstraints); } void CSimplex::Init(int numVariables, int numConstraints) { Destruct(); m_numVariables = numVariables; m_numConstraints = numConstraints; m_pTableau = new float[(NumRows()+1) * NumColumns()]; m_pInitialTableau = new float[(NumRows()+1) * NumColumns()]; m_pSolution = m_pTableau + NumRows() * NumColumns(); // allocating basis and non-basis indices in one call m_pBasis = new int[m_numConstraints + m_numVariables]; m_pNonBasis = m_pBasis + m_numConstraints; m_state = kUnknown; } void CSimplex::PrintTableau()const { Msg("problem.Init(%d,%d);\nfloat test[%d]={", m_numVariables, m_numConstraints, (m_numVariables+1)*(m_numConstraints+1)); for(int i = 0; i < NumRows(); ++i) { for(int j = 0;j < NumColumns(); ++j) { Msg(" %g,",Tableau(i,j)); } Msg("\n"); } Msg("}"); } void CSimplex::InitTableau(const float *pTableau) { const float *p = pTableau; for(int nRow = 0; nRow <= m_numConstraints; ++nRow) { for(int nColumn = 0; nColumn < m_numVariables; ++nColumn) { Tableau(nRow, nColumn) = *(p++); } Tableau(nRow, NumColumns()-1) = *(p++); } } CSimplex::~CSimplex() { Destruct(); } void CSimplex::Destruct() { delete[]m_pInitialTableau; m_pInitialTableau = NULL; delete[]m_pTableau; m_pTableau = NULL; delete[]m_pBasis; m_pBasis = NULL; } CSimplex::StateEnum CSimplex::Solve(float flThreshold, int maxStallIterations) { m_state = kUnknown; PrepareTableau(); if(SolvePhase1(flThreshold, maxStallIterations) == kUnknown) SolvePhase2(flThreshold, maxStallIterations); GatherSolution(); return m_state; } /////////////////////////////////////////////////////////////////////////// // bring constraints to b>=0 form for phase-2 full solution CSimplex::StateEnum CSimplex::SolvePhase1(float flThreshold, int maxStallIterations) { for(int nPotentiallyInfiniteLoop = 0; nPotentiallyInfiniteLoop < maxStallIterations; ++nPotentiallyInfiniteLoop) { if(!IteratePhase1()) break; } return m_state; } ////////////////////////////////////////////////////////////////////////// // Solve the linear problem ; // \param flThreshold - this is how much we need to improve objective every step that's not considered lost // \param maxStallIterations - this is how many "lost" (see flThreshold) steps we may take before we bail // CSimplex::StateEnum CSimplex::SolvePhase2(float flThreshold, int maxStallIterations) { for(int nPotentiallyInfiniteLoop = 0; nPotentiallyInfiniteLoop < maxStallIterations; ++nPotentiallyInfiniteLoop) { if(!IteratePhase2()) break; } Validate(); return m_state; } // fill out m-pSolution array (primal solution) void CSimplex::GatherSolution() { // Notes: // PRIMAL SOLUTION is indicated by the rightmost column of the tableau; // there are at most m_numConstraint basic variables that participate in the solution. // The original problem PRIMAL unknowns are numbered 0..m_numVariables; the rest (m_numVariables+1..m_numVariables+m_numConstraints) are the PRIMAL SLACK variables // DUAL SOLUTION is in the row [m_numConstraints], and it's basic variables are indicated by m_pNonBasic array and are reversed: // first the DUAL SLACK variables are numbered 0..m_numVariables; the rest (m_numVariables+1..m_numVariables+m_numConstraints) are the DUAL variables memset(m_pSolution, 0, sizeof(*m_pSolution) * NumColumns()); // initial value of all X's are 0's for(int nRow = 0; nRow < m_numConstraints; ++nRow) { int nBasisVariable = m_pBasis[nRow]; m_pSolution[nBasisVariable] = Tableau(nRow, NumColumns()-1); } m_pSolution[m_numVariables+m_numConstraints] = Tableau(m_numConstraints, NumColumns()-1); } /////////////////////////////////////////////////////////////////////////// // Find and pivot a row with negative constraint const (right side) // return false - if can't find such constraint or can't pivot // bool CSimplex::IteratePhase1() { int nFixRow = FindLastNegConstrRow(); if(nFixRow < 0) return false; // phase 1 complete: no rows to fix int nPivotColumn = ChooseNegativeElementInRow(nFixRow); if(nPivotColumn < 0) { m_state = kInfeasible; return false; } int nPivotRow = nFixRow; float flMinimizer = Tableau (nPivotRow, NumColumns()-1)/Tableau(nPivotRow, nPivotColumn); // minimize this // UNTESTED! What's the rule to choose pivot in phase1? for(int nCandidatePivotRow = nPivotRow + 1; nCandidatePivotRow < m_numConstraints; ++nCandidatePivotRow) { float flCandidateConst = Tableau (nCandidatePivotRow,NumColumns()-1), flCandidatePivot = Tableau (nCandidatePivotRow, nPivotColumn); if ( flCandidateConst < 0 && flCandidatePivot > 1e-6f ) { float flCandidateMinimizer = flCandidateConst / flCandidatePivot; if(flCandidateMinimizer < flMinimizer) { flCandidateMinimizer = flMinimizer; nPivotRow = nCandidatePivotRow; // UNTESTED! } } } return Pivot(nPivotRow, nPivotColumn); } ////////////////////////////////////////////////////////////////////////// // Return the index of the last row with negative Constraint Const (b[i] in A.x<=b formulation) int CSimplex::FindLastNegConstrRow() { int nFixRow = -1; for(int nRow = 0; nRow < m_numConstraints; ++nRow) { if(Tableau(nRow, NumColumns()-1) < 0) { nFixRow = nRow; } } return nFixRow; } /////////////////////////////////////////////////////////////////////////// // Choose some (e.g. the most negative) negative number in the row int CSimplex::ChooseNegativeElementInRow(int nFixRow) { int indexNegElement = -1; float flMinElement = 0; for(int nColumn = 0; nColumn < m_numVariables; ++nColumn) { float flElement = Tableau(nFixRow, nColumn); if(flElement < flMinElement) { indexNegElement = nColumn; flMinElement = flElement; } } return indexNegElement; } bool CSimplex::IteratePhase2() { int nPivotColumn = FindPivotColumn(); if(nPivotColumn < 0) { m_state = kOptimal; return false; } int nPivotRow = FindPivotRow(nPivotColumn); if(nPivotRow < 0) { m_state = kUnbound; return false; } bool ok = Pivot(nPivotRow, nPivotColumn); // since we replaced the basis variable, we have to replace its corresponding column return ok; } ////////////////////////////////////////////////////////////////////////// // Self-explanatory, isn't it? bool CSimplex::Pivot(int nPivotRow, int nPivotColumn) { if(fabs(Tableau(nPivotRow, nPivotColumn)) < 1e-8f) { m_state = kCannotPivot; return false; // Can NOT pivot on zero :( choose another (ie. fancier) pivot rule } /// get the 1/Tij, then replace the multiplied element with it float flFactor = 1.0f / Tableau(nPivotRow, nPivotColumn); MultiplyRow(nPivotRow, flFactor); for(int i = 0; i <= m_numConstraints; ++i) { if(i != nPivotRow) { float flFactorOther = -Tableau(i,nPivotColumn); AddRowMulFactor(i, nPivotRow, flFactorOther); Tableau(i,nPivotColumn) = flFactorOther * flFactor; // replace the column with original column / -pivot } } Tableau(nPivotRow, nPivotColumn) = flFactor; int nEnteringVariable = m_pNonBasis[nPivotColumn]; int nExitingVariable = m_pBasis[nPivotRow]; // remember the index of the entering new basis var m_pBasis[nPivotRow] = nEnteringVariable; m_pNonBasis[nPivotColumn] = nExitingVariable; Validate(); return true; } ////////////////////////////////////////////////////////////////////////// // find the column with the most negative number in the last (objective) row int CSimplex::FindPivotColumn() { int nBest = -1; float flBest = 0; for(int i = 0; i < m_numVariables; ++i) { float flElement = Tableau(m_numConstraints, i); if(flElement > flBest) { flBest = flElement; nBest = i; } } if(nBest < 0) { m_state = kOptimal; return -1; } else return nBest; }; int CSimplex::FindPivotRow(int nColumn) { float flBest = FLT_MAX; int nBest = -1; for(int nRow = 0; nRow < m_numConstraints; ++nRow) { float flPivotCandidate = Tableau(nRow, nColumn); if(flPivotCandidate > 1e-6f) { // don't perform any tests unless flTest is finite float flTest = Tableau(nRow, NumColumns()-1) / flPivotCandidate; if(flTest < flBest) { // flBest is either Infinity or is worse; it's worse in any case, so replace it flBest = flTest; nBest = nRow; } } } return nBest; } void CSimplex::MultiplyRow(int nRow, float flFactor) { for(int nColumn = 0; nColumn < NumColumns(); ++nColumn) { Tableau(nRow, nColumn) *= flFactor; } } void CSimplex::AddRowMulFactor(int nTargetRow, int nPivotRow, float fFactor) { for(int nColumn = 0; nColumn < NumColumns(); ++nColumn) { Tableau(nTargetRow, nColumn) += Tableau(nPivotRow, nColumn) * fFactor; } } // set the I matrix in the slack columns of the tableau void CSimplex::PrepareTableau() { /* for(int nRow = 0; nRow < m_numConstraints + 1; ++nRow) { for(int nColumn = 0; nColumn < m_numConstraints; ++nColumn) Tableau(nRow, nColumn + m_numVariables) = 0; } */ for(int nonBasis = 0; nonBasis < m_numVariables; ++nonBasis) { m_pNonBasis[nonBasis] = nonBasis; } for(int nConstraint = 0; nConstraint < m_numConstraints; ++nConstraint) { m_pBasis[nConstraint] = m_numVariables + nConstraint; // slack variables //Tableau(nConstraint, nConstraint + m_numVariables) = 1.0f; } //m_pSolution[m_numVariables+m_numConstraints] = Tableau(m_numConstraints, NumColumns()-1) = 0.0f; // starting with "0" objective, and all "0" variables memcpy(m_pInitialTableau,m_pTableau,(NumRows()+1) * NumColumns() * sizeof(float)); } void CSimplex::SetConstraintConst(int nConstraint, float fConst) { m_pSolution[m_numVariables + nConstraint] = Tableau(nConstraint, NumColumns()-1) = fConst; } void CSimplex::SetConstraintFactor(int nConstraint, int nConstant, float fFactor) { Tableau(nConstraint, nConstant) = fFactor; } void CSimplex::SetObjectiveFactor(int nConstant, float fFactor) { // the objective factor is negated because for the objective P = cx , we write it as -c x + P -> max Tableau(m_numConstraints, nConstant) = fFactor; } void CSimplex::SetObjectiveFactors(int numFactors, const float *pFactors) { Assert(numFactors == m_numVariables); for(int i =0; i < m_numVariables && i < numFactors; ++i) SetObjectiveFactor(i,pFactors[i]); } float CSimplex::GetSolution(int nVariable)const { Assert(nVariable < m_numVariables); return m_pSolution[nVariable]; } float CSimplex::GetSlack(int nConstraint)const { Assert(nConstraint < m_numConstraints); return m_pSolution[m_numVariables + nConstraint]; } float CSimplex::GetObjective()const { /* float flResult = 0; for(int i = 0; i < m_numVariables + m_numConstraints; ++i) flResult -= m_pSolution[i] * Tableau(m_numConstraints,i); return flResult; */ return Tableau(m_numConstraints, NumColumns()-1); } void CSimplex::Validate() { #if defined(_DEBUG) && 0 GatherSolution(); for(int i = 0; i <= m_numConstraints; ++i) { float flRes = 0; for(int j = 0; j < m_numVariables; ++j) flRes += GetInitialTableau(i,j) * m_pSolution[j]; if(i == m_numConstraints) { Msg("Objective = %g; basis:",flRes); for (int j = 0; j < m_numVariables; ++j) Msg(" %g", m_pSolution[j]); Msg(" |slacks:"); for(int j = 0; j < m_numConstraints; ++j) Msg(" %g", m_pSolution[j+m_numVariables]); Msg("\n"); } else Msg("%g\t<= %g\n", flRes, GetInitialTableau(i,NumColumns()-1)); } #endif } class CSimplexTestUnit { public: CSimplexTestUnit() { CSimplex test(3,2); test.SetObjectiveFactor(0, 12); test.SetObjectiveFactor(1, 8); test.SetObjectiveFactor(2, 24); test.SetConstraintFactor(0, 0, 6); test.SetConstraintFactor(0, 1, 2); test.SetConstraintFactor(0, 2, 4); test.SetConstraintConst(0, 200); test.SetConstraintFactor(1, 0, 2); test.SetConstraintFactor(1, 1, 2); test.SetConstraintFactor(1, 2, 12); test.SetConstraintConst(1, 160); test.Solve(); test.Init(2,2); float test2[] = {2,1,3, 3,1,4, 17,5,0}; test.InitTableau(test2); test.Solve(); // m_pSolution (test.m_pSolution) should be : 30 40 | 0 0 | 4100 ////////////////////////////////////////////////////////////////////////// // unbound-solution problem: x1-x2<=1 && x2-x1<=1, maximize x1+x2; if x1==x2, we can go unbound x1==x2 -> +inf // the dual formulation is infeasible in this case: v2-v1 >= 1 && v1-v2 >= 1, which are self-contradictory test.Init(2,2); float testUnsolvable[] = {-1,1,1, 1,-1,1, 1,1,0}; test.InitTableau(testUnsolvable); test.Solve(); ////////////////////////////////////////////////////////////////////////// // General Simplex problem: equality constraint test.Init(2, 3); float testGenSimplex[] = {1,1,20, 1,2,30, -1,-2,-30, 2,1,0}; test.InitTableau(testGenSimplex); test.Solve(); test.Init(7,6); float testA[56]={ -1, 1, 0, -0, -0, 0, 1, 13.0048, 1, -1, 0, -0, -0, 0, 1, 13.0048, 0, -0, -1, 1, -0, 0, 1, 13.0048, 0, -0, 1, -1, -0, 0, 1, 13.0048, 0, -0, 0, -0, 1, -1, 1, 0.00100005, 0, -0, 0, -0, -1, 1, 1, 0.405401, 0, 0, 0, 0, 0, 0, 1, 0 }; test.InitTableau(testA); test.Solve(); } }; // this is for debugging and unit-testing //static CSimplexTestUnit s_test;