Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

495 lines
13 KiB

  1. #include <basetypes.h>
  2. #include <float.h>
  3. #include "simplex.h"
  4. // a nice tutorial on simplex method: http://math.uww.edu/~mcfarlat/ism.htm
  5. CSimplex::CSimplex():
  6. m_numVariables(0),m_numConstraints(0),m_pTableau(0),m_pInitialTableau(0), m_pSolution(0), m_pBasis(0)
  7. {
  8. }
  9. CSimplex::CSimplex(int numVariables, int numConstraints):
  10. m_numVariables(0),m_numConstraints(0),m_pTableau(0),m_pInitialTableau(0), m_pSolution(0), m_pBasis(0)
  11. {
  12. Init(numVariables, numConstraints);
  13. }
  14. void CSimplex::Init(int numVariables, int numConstraints)
  15. {
  16. Destruct();
  17. m_numVariables = numVariables; m_numConstraints = numConstraints;
  18. m_pTableau = new float[(NumRows()+1) * NumColumns()];
  19. m_pInitialTableau = new float[(NumRows()+1) * NumColumns()];
  20. m_pSolution = m_pTableau + NumRows() * NumColumns();
  21. // allocating basis and non-basis indices in one call
  22. m_pBasis = new int[m_numConstraints + m_numVariables];
  23. m_pNonBasis = m_pBasis + m_numConstraints;
  24. m_state = kUnknown;
  25. }
  26. void CSimplex::PrintTableau()const
  27. {
  28. Msg("problem.Init(%d,%d);\nfloat test[%d]={", m_numVariables, m_numConstraints, (m_numVariables+1)*(m_numConstraints+1));
  29. for(int i = 0; i < NumRows(); ++i)
  30. {
  31. for(int j = 0;j < NumColumns(); ++j)
  32. {
  33. Msg(" %g,",Tableau(i,j));
  34. }
  35. Msg("\n");
  36. }
  37. Msg("}");
  38. }
  39. void CSimplex::InitTableau(const float *pTableau)
  40. {
  41. const float *p = pTableau;
  42. for(int nRow = 0; nRow <= m_numConstraints; ++nRow)
  43. {
  44. for(int nColumn = 0; nColumn < m_numVariables; ++nColumn)
  45. {
  46. Tableau(nRow, nColumn) = *(p++);
  47. }
  48. Tableau(nRow, NumColumns()-1) = *(p++);
  49. }
  50. }
  51. CSimplex::~CSimplex()
  52. {
  53. Destruct();
  54. }
  55. void CSimplex::Destruct()
  56. {
  57. delete[]m_pInitialTableau;
  58. m_pInitialTableau = NULL;
  59. delete[]m_pTableau;
  60. m_pTableau = NULL;
  61. delete[]m_pBasis;
  62. m_pBasis = NULL;
  63. }
  64. CSimplex::StateEnum CSimplex::Solve(float flThreshold, int maxStallIterations)
  65. {
  66. m_state = kUnknown;
  67. PrepareTableau();
  68. if(SolvePhase1(flThreshold, maxStallIterations) == kUnknown)
  69. SolvePhase2(flThreshold, maxStallIterations);
  70. GatherSolution();
  71. return m_state;
  72. }
  73. ///////////////////////////////////////////////////////////////////////////
  74. // bring constraints to b>=0 form for phase-2 full solution
  75. CSimplex::StateEnum CSimplex::SolvePhase1(float flThreshold, int maxStallIterations)
  76. {
  77. for(int nPotentiallyInfiniteLoop = 0; nPotentiallyInfiniteLoop < maxStallIterations; ++nPotentiallyInfiniteLoop)
  78. {
  79. if(!IteratePhase1())
  80. break;
  81. }
  82. return m_state;
  83. }
  84. //////////////////////////////////////////////////////////////////////////
  85. // Solve the linear problem ;
  86. // \param flThreshold - this is how much we need to improve objective every step that's not considered lost
  87. // \param maxStallIterations - this is how many "lost" (see flThreshold) steps we may take before we bail
  88. //
  89. CSimplex::StateEnum CSimplex::SolvePhase2(float flThreshold, int maxStallIterations)
  90. {
  91. for(int nPotentiallyInfiniteLoop = 0; nPotentiallyInfiniteLoop < maxStallIterations; ++nPotentiallyInfiniteLoop)
  92. {
  93. if(!IteratePhase2())
  94. break;
  95. }
  96. Validate();
  97. return m_state;
  98. }
  99. // fill out m-pSolution array (primal solution)
  100. void CSimplex::GatherSolution()
  101. {
  102. // Notes:
  103. // PRIMAL SOLUTION is indicated by the rightmost column of the tableau;
  104. // there are at most m_numConstraint basic variables that participate in the solution.
  105. // The original problem PRIMAL unknowns are numbered 0..m_numVariables; the rest (m_numVariables+1..m_numVariables+m_numConstraints) are the PRIMAL SLACK variables
  106. // DUAL SOLUTION is in the row [m_numConstraints], and it's basic variables are indicated by m_pNonBasic array and are reversed:
  107. // first the DUAL SLACK variables are numbered 0..m_numVariables; the rest (m_numVariables+1..m_numVariables+m_numConstraints) are the DUAL variables
  108. memset(m_pSolution, 0, sizeof(*m_pSolution) * NumColumns()); // initial value of all X's are 0's
  109. for(int nRow = 0; nRow < m_numConstraints; ++nRow)
  110. {
  111. int nBasisVariable = m_pBasis[nRow];
  112. m_pSolution[nBasisVariable] = Tableau(nRow, NumColumns()-1);
  113. }
  114. m_pSolution[m_numVariables+m_numConstraints] = Tableau(m_numConstraints, NumColumns()-1);
  115. }
  116. ///////////////////////////////////////////////////////////////////////////
  117. // Find and pivot a row with negative constraint const (right side)
  118. // return false - if can't find such constraint or can't pivot
  119. //
  120. bool CSimplex::IteratePhase1()
  121. {
  122. int nFixRow = FindLastNegConstrRow();
  123. if(nFixRow < 0)
  124. return false; // phase 1 complete: no rows to fix
  125. int nPivotColumn = ChooseNegativeElementInRow(nFixRow);
  126. if(nPivotColumn < 0)
  127. {
  128. m_state = kInfeasible;
  129. return false;
  130. }
  131. int nPivotRow = nFixRow;
  132. float flMinimizer = Tableau (nPivotRow, NumColumns()-1)/Tableau(nPivotRow, nPivotColumn); // minimize this
  133. // UNTESTED! What's the rule to choose pivot in phase1?
  134. for(int nCandidatePivotRow = nPivotRow + 1; nCandidatePivotRow < m_numConstraints; ++nCandidatePivotRow)
  135. {
  136. float flCandidateConst = Tableau (nCandidatePivotRow,NumColumns()-1), flCandidatePivot = Tableau (nCandidatePivotRow, nPivotColumn);
  137. if ( flCandidateConst < 0 && flCandidatePivot > 1e-6f )
  138. {
  139. float flCandidateMinimizer = flCandidateConst / flCandidatePivot;
  140. if(flCandidateMinimizer < flMinimizer)
  141. {
  142. flCandidateMinimizer = flMinimizer;
  143. nPivotRow = nCandidatePivotRow; // UNTESTED!
  144. }
  145. }
  146. }
  147. return Pivot(nPivotRow, nPivotColumn);
  148. }
  149. //////////////////////////////////////////////////////////////////////////
  150. // Return the index of the last row with negative Constraint Const (b[i] in A.x<=b formulation)
  151. int CSimplex::FindLastNegConstrRow()
  152. {
  153. int nFixRow = -1;
  154. for(int nRow = 0; nRow < m_numConstraints; ++nRow)
  155. {
  156. if(Tableau(nRow, NumColumns()-1) < 0)
  157. {
  158. nFixRow = nRow;
  159. }
  160. }
  161. return nFixRow;
  162. }
  163. ///////////////////////////////////////////////////////////////////////////
  164. // Choose some (e.g. the most negative) negative number in the row
  165. int CSimplex::ChooseNegativeElementInRow(int nFixRow)
  166. {
  167. int indexNegElement = -1;
  168. float flMinElement = 0;
  169. for(int nColumn = 0; nColumn < m_numVariables; ++nColumn)
  170. {
  171. float flElement = Tableau(nFixRow, nColumn);
  172. if(flElement < flMinElement)
  173. {
  174. indexNegElement = nColumn;
  175. flMinElement = flElement;
  176. }
  177. }
  178. return indexNegElement;
  179. }
  180. bool CSimplex::IteratePhase2()
  181. {
  182. int nPivotColumn = FindPivotColumn();
  183. if(nPivotColumn < 0)
  184. {
  185. m_state = kOptimal;
  186. return false;
  187. }
  188. int nPivotRow = FindPivotRow(nPivotColumn);
  189. if(nPivotRow < 0)
  190. {
  191. m_state = kUnbound;
  192. return false;
  193. }
  194. bool ok = Pivot(nPivotRow, nPivotColumn);
  195. // since we replaced the basis variable, we have to replace its corresponding column
  196. return ok;
  197. }
  198. //////////////////////////////////////////////////////////////////////////
  199. // Self-explanatory, isn't it?
  200. bool CSimplex::Pivot(int nPivotRow, int nPivotColumn)
  201. {
  202. if(fabs(Tableau(nPivotRow, nPivotColumn)) < 1e-8f)
  203. {
  204. m_state = kCannotPivot;
  205. return false; // Can NOT pivot on zero :( choose another (ie. fancier) pivot rule
  206. }
  207. /// get the 1/Tij, then replace the multiplied element with it
  208. float flFactor = 1.0f / Tableau(nPivotRow, nPivotColumn);
  209. MultiplyRow(nPivotRow, flFactor);
  210. for(int i = 0; i <= m_numConstraints; ++i)
  211. {
  212. if(i != nPivotRow)
  213. {
  214. float flFactorOther = -Tableau(i,nPivotColumn);
  215. AddRowMulFactor(i, nPivotRow, flFactorOther);
  216. Tableau(i,nPivotColumn) = flFactorOther * flFactor; // replace the column with original column / -pivot
  217. }
  218. }
  219. Tableau(nPivotRow, nPivotColumn) = flFactor;
  220. int nEnteringVariable = m_pNonBasis[nPivotColumn];
  221. int nExitingVariable = m_pBasis[nPivotRow];
  222. // remember the index of the entering new basis var
  223. m_pBasis[nPivotRow] = nEnteringVariable;
  224. m_pNonBasis[nPivotColumn] = nExitingVariable;
  225. Validate();
  226. return true;
  227. }
  228. //////////////////////////////////////////////////////////////////////////
  229. // find the column with the most negative number in the last (objective) row
  230. int CSimplex::FindPivotColumn()
  231. {
  232. int nBest = -1;
  233. float flBest = 0;
  234. for(int i = 0; i < m_numVariables; ++i)
  235. {
  236. float flElement = Tableau(m_numConstraints, i);
  237. if(flElement > flBest)
  238. {
  239. flBest = flElement;
  240. nBest = i;
  241. }
  242. }
  243. if(nBest < 0)
  244. {
  245. m_state = kOptimal;
  246. return -1;
  247. }
  248. else
  249. return nBest;
  250. };
  251. int CSimplex::FindPivotRow(int nColumn)
  252. {
  253. float flBest = FLT_MAX;
  254. int nBest = -1;
  255. for(int nRow = 0; nRow < m_numConstraints; ++nRow)
  256. {
  257. float flPivotCandidate = Tableau(nRow, nColumn);
  258. if(flPivotCandidate > 1e-6f)
  259. {
  260. // don't perform any tests unless flTest is finite
  261. float flTest = Tableau(nRow, NumColumns()-1) / flPivotCandidate;
  262. if(flTest < flBest)
  263. {
  264. // flBest is either Infinity or is worse; it's worse in any case, so replace it
  265. flBest = flTest;
  266. nBest = nRow;
  267. }
  268. }
  269. }
  270. return nBest;
  271. }
  272. void CSimplex::MultiplyRow(int nRow, float flFactor)
  273. {
  274. for(int nColumn = 0; nColumn < NumColumns(); ++nColumn)
  275. {
  276. Tableau(nRow, nColumn) *= flFactor;
  277. }
  278. }
  279. void CSimplex::AddRowMulFactor(int nTargetRow, int nPivotRow, float fFactor)
  280. {
  281. for(int nColumn = 0; nColumn < NumColumns(); ++nColumn)
  282. {
  283. Tableau(nTargetRow, nColumn) += Tableau(nPivotRow, nColumn) * fFactor;
  284. }
  285. }
  286. // set the I matrix in the slack columns of the tableau
  287. void CSimplex::PrepareTableau()
  288. {
  289. /*
  290. for(int nRow = 0; nRow < m_numConstraints + 1; ++nRow)
  291. {
  292. for(int nColumn = 0; nColumn < m_numConstraints; ++nColumn)
  293. Tableau(nRow, nColumn + m_numVariables) = 0;
  294. }
  295. */
  296. for(int nonBasis = 0; nonBasis < m_numVariables; ++nonBasis)
  297. {
  298. m_pNonBasis[nonBasis] = nonBasis;
  299. }
  300. for(int nConstraint = 0; nConstraint < m_numConstraints; ++nConstraint)
  301. {
  302. m_pBasis[nConstraint] = m_numVariables + nConstraint; // slack variables
  303. //Tableau(nConstraint, nConstraint + m_numVariables) = 1.0f;
  304. }
  305. //m_pSolution[m_numVariables+m_numConstraints] =
  306. Tableau(m_numConstraints, NumColumns()-1) = 0.0f; // starting with "0" objective, and all "0" variables
  307. memcpy(m_pInitialTableau,m_pTableau,(NumRows()+1) * NumColumns() * sizeof(float));
  308. }
  309. void CSimplex::SetConstraintConst(int nConstraint, float fConst)
  310. {
  311. m_pSolution[m_numVariables + nConstraint] = Tableau(nConstraint, NumColumns()-1) = fConst;
  312. }
  313. void CSimplex::SetConstraintFactor(int nConstraint, int nConstant, float fFactor)
  314. {
  315. Tableau(nConstraint, nConstant) = fFactor;
  316. }
  317. void CSimplex::SetObjectiveFactor(int nConstant, float fFactor)
  318. {
  319. // the objective factor is negated because for the objective P = cx , we write it as -c x + P -> max
  320. Tableau(m_numConstraints, nConstant) = fFactor;
  321. }
  322. void CSimplex::SetObjectiveFactors(int numFactors, const float *pFactors)
  323. {
  324. Assert(numFactors == m_numVariables);
  325. for(int i =0; i < m_numVariables && i < numFactors; ++i)
  326. SetObjectiveFactor(i,pFactors[i]);
  327. }
  328. float CSimplex::GetSolution(int nVariable)const
  329. {
  330. Assert(nVariable < m_numVariables);
  331. return m_pSolution[nVariable];
  332. }
  333. float CSimplex::GetSlack(int nConstraint)const
  334. {
  335. Assert(nConstraint < m_numConstraints);
  336. return m_pSolution[m_numVariables + nConstraint];
  337. }
  338. float CSimplex::GetObjective()const
  339. {
  340. /*
  341. float flResult = 0;
  342. for(int i = 0; i < m_numVariables + m_numConstraints; ++i)
  343. flResult -= m_pSolution[i] * Tableau(m_numConstraints,i);
  344. return flResult;
  345. */
  346. return Tableau(m_numConstraints, NumColumns()-1);
  347. }
  348. void CSimplex::Validate()
  349. {
  350. #if defined(_DEBUG) && 0
  351. GatherSolution();
  352. for(int i = 0; i <= m_numConstraints; ++i)
  353. {
  354. float flRes = 0;
  355. for(int j = 0; j < m_numVariables; ++j)
  356. flRes += GetInitialTableau(i,j) * m_pSolution[j];
  357. if(i == m_numConstraints)
  358. {
  359. Msg("Objective = %g; basis:",flRes);
  360. for (int j = 0; j < m_numVariables; ++j)
  361. Msg(" %g", m_pSolution[j]);
  362. Msg(" |slacks:");
  363. for(int j = 0; j < m_numConstraints; ++j)
  364. Msg(" %g", m_pSolution[j+m_numVariables]);
  365. Msg("\n");
  366. }
  367. else
  368. Msg("%g\t<= %g\n", flRes, GetInitialTableau(i,NumColumns()-1));
  369. }
  370. #endif
  371. }
  372. class CSimplexTestUnit
  373. {
  374. public:
  375. CSimplexTestUnit()
  376. {
  377. CSimplex test(3,2);
  378. test.SetObjectiveFactor(0, 12);
  379. test.SetObjectiveFactor(1, 8);
  380. test.SetObjectiveFactor(2, 24);
  381. test.SetConstraintFactor(0, 0, 6);
  382. test.SetConstraintFactor(0, 1, 2);
  383. test.SetConstraintFactor(0, 2, 4);
  384. test.SetConstraintConst(0, 200);
  385. test.SetConstraintFactor(1, 0, 2);
  386. test.SetConstraintFactor(1, 1, 2);
  387. test.SetConstraintFactor(1, 2, 12);
  388. test.SetConstraintConst(1, 160);
  389. test.Solve();
  390. test.Init(2,2);
  391. float test2[] = {2,1,3, 3,1,4, 17,5,0};
  392. test.InitTableau(test2);
  393. test.Solve();
  394. // m_pSolution (test.m_pSolution) should be : 30 40 | 0 0 | 4100
  395. //////////////////////////////////////////////////////////////////////////
  396. // unbound-solution problem: x1-x2<=1 && x2-x1<=1, maximize x1+x2; if x1==x2, we can go unbound x1==x2 -> +inf
  397. // the dual formulation is infeasible in this case: v2-v1 >= 1 && v1-v2 >= 1, which are self-contradictory
  398. test.Init(2,2);
  399. float testUnsolvable[] = {-1,1,1, 1,-1,1, 1,1,0};
  400. test.InitTableau(testUnsolvable);
  401. test.Solve();
  402. //////////////////////////////////////////////////////////////////////////
  403. // General Simplex problem: equality constraint
  404. test.Init(2, 3);
  405. float testGenSimplex[] = {1,1,20, 1,2,30, -1,-2,-30, 2,1,0};
  406. test.InitTableau(testGenSimplex);
  407. test.Solve();
  408. test.Init(7,6);
  409. float testA[56]={ -1, 1, 0, -0, -0, 0, 1, 13.0048,
  410. 1, -1, 0, -0, -0, 0, 1, 13.0048,
  411. 0, -0, -1, 1, -0, 0, 1, 13.0048,
  412. 0, -0, 1, -1, -0, 0, 1, 13.0048,
  413. 0, -0, 0, -0, 1, -1, 1, 0.00100005,
  414. 0, -0, 0, -0, -1, 1, 1, 0.405401,
  415. 0, 0, 0, 0, 0, 0, 1, 0
  416. };
  417. test.InitTableau(testA);
  418. test.Solve();
  419. }
  420. };
  421. // this is for debugging and unit-testing
  422. //static CSimplexTestUnit s_test;