496 lines
13 KiB
C++
496 lines
13 KiB
C++
|
#include <basetypes.h>
|
||
|
#include <float.h>
|
||
|
#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;
|