DownhillSimplexOptimizer.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. /**
  2. *
  3. * @file: DownhillSimplexOptimizer.cpp: implementation of the downhill Simplex Optimier
  4. *
  5. * @author: Matthias Wacker, Esther Platzer, Alexander Freytag
  6. *
  7. */
  8. #include <iostream>
  9. // #include "mateigen.h" // for SVD
  10. #include "core/optimization/blackbox/DownhillSimplexOptimizer.h"
  11. #include "core/optimization/blackbox/Definitions_core_opt.h"
  12. using namespace OPTIMIZATION;
  13. DownhillSimplexOptimizer::DownhillSimplexOptimizer(OptLogBase *loger): SuperClass(loger)
  14. {
  15. m_abort = false;
  16. m_simplexInitialized = false;
  17. //note that we use the negative value of alpha in amoeba
  18. m_alpha = 1.0;
  19. m_beta = 0.5;
  20. // m_gamma was previously 1.0, but this actually deactivates extrapolation. We use 2.0 as suggested in Num. Recipes
  21. m_gamma = 2.0;
  22. m_rankdeficiencythresh = 0.01;
  23. m_rankcheckenabled = false;
  24. }
  25. DownhillSimplexOptimizer::DownhillSimplexOptimizer(const DownhillSimplexOptimizer &opt) : SuperClass(opt)
  26. {
  27. m_abort = opt.m_abort;
  28. m_simplexInitialized = opt.m_simplexInitialized;
  29. m_vertices = opt.m_vertices;
  30. m_y = opt.m_y;
  31. m_alpha = opt.m_alpha;
  32. m_beta = opt.m_beta;
  33. m_gamma = opt.m_gamma;
  34. m_rankdeficiencythresh= 0.01;
  35. m_rankcheckenabled= false;
  36. }
  37. DownhillSimplexOptimizer::~DownhillSimplexOptimizer()
  38. {
  39. }
  40. bool DownhillSimplexOptimizer::setWholeSimplex(const matrix_type &simplex)
  41. {
  42. if((int)simplex.rows() == static_cast<int>(m_numberOfParameters) && (int)simplex.cols() == static_cast<int>(m_numberOfParameters + 1))
  43. {
  44. //Check if simplex has rank(simplex)=numberOfParameters
  45. //otherwise return false because of bad posed simplex
  46. if(m_rankcheckenabled)
  47. {
  48. //TODO do we need this?!?!?!
  49. int rank = 0;
  50. // getRank(simplex, m_rankdeficiencythresh);
  51. if(rank < static_cast<int>(m_numberOfParameters))
  52. {
  53. m_simplexInitialized = false;
  54. return false;
  55. }
  56. }
  57. m_vertices = simplex;
  58. m_simplexInitialized = true;
  59. for (int k = 0; k < static_cast<int>(m_numberOfParameters +1); k++)
  60. {
  61. m_y(k,0) = evaluateCostFunction(m_vertices(0,k,m_numberOfParameters-1,k));
  62. }
  63. return true;
  64. }
  65. else
  66. {
  67. m_simplexInitialized = false;
  68. return false;
  69. }
  70. }
  71. void DownhillSimplexOptimizer::init()
  72. {
  73. SuperClass::init();
  74. // allocate matrices
  75. m_vertices = matrix_type(m_numberOfParameters, m_numberOfParameters + 1);
  76. m_y = matrix_type(m_numberOfParameters + 1,1);
  77. m_abort = false;
  78. //this forces a re-initialization in all cases
  79. //it is even more advisable, if we would perform a random initialization
  80. //remark of erik: there was a bug here
  81. m_simplexInitialized = false;
  82. // previously, we did a random initialization
  83. // right now, we rely on our first guess and move it in one dimension by m_scales[i]
  84. // as suggested in Numerical Recipes
  85. if (m_simplexInitialized == false)
  86. {
  87. //this aborting stuff was formerly needed to ensure a valid simplex for random initializations
  88. bool abort = false;
  89. while(abort == false)
  90. {
  91. matrix_type simplex(m_numberOfParameters,m_numberOfParameters+1);
  92. //move every point in a single dimension by m_scales[i]
  93. //we first iterate over dimensions
  94. for(int i = 0; i < static_cast<int>(m_numberOfParameters); i++)
  95. {
  96. //and internally over the different points
  97. for(int j = 0; j < static_cast<int>(m_numberOfParameters+1); j++)
  98. {
  99. simplex(i,j) = m_parameters(i,0);
  100. if( j == i+1 )
  101. {
  102. double tmpRand = m_scales(i,0);// * ((double)rand()) / RAND_MAX;
  103. simplex(i,j) += tmpRand;
  104. }
  105. }
  106. }
  107. //evaluate function values in simplex corners and
  108. //check if simplex is degenerated (less than d dimensions)
  109. if(this->setWholeSimplex(simplex) == true)
  110. {
  111. // simplexInitializen is only to indicate manual initialization ..
  112. //actually, this is not needed anymore.
  113. m_simplexInitialized = false;
  114. //we computed a valid solution, so break the while loop
  115. //actually, this is always the case, since we de-activated the random init
  116. abort =true;
  117. }
  118. }
  119. }
  120. // if the simplex was already initialized, we only have to compute the function values
  121. // of its corners
  122. else
  123. {
  124. //compute the function values of the initial simplex points
  125. for (int k = 0; k < static_cast<int>(m_numberOfParameters +1); k++)
  126. {
  127. m_y(k,0) = evaluateCostFunction(m_vertices(0,k,m_numberOfParameters-1,k));
  128. }
  129. }
  130. }
  131. int DownhillSimplexOptimizer::optimize()
  132. {
  133. //before optimizing, we initialize our simplex in all cases
  134. // if you are pretty sure, that you already have a suitable initial
  135. // simplex, you can skip this part
  136. //if(m_simplexInitialized == false)
  137. //{
  138. this->init();
  139. //}
  140. if(m_loger)
  141. m_loger->logTrace("Starting Downhill Simplex Optimization\n");
  142. //tmp contains the index of the best vertex point after running DHS
  143. int tmp=amoeba();
  144. m_parameters = m_vertices(0,tmp,m_numberOfParameters-1,tmp);
  145. //final evaluation of the cost function with our optimal parameter settings
  146. m_currentCostFunctionValue = evaluateCostFunction(m_parameters);
  147. return m_returnReason;
  148. }
  149. /*
  150. * Taken from Numerical Recipies in C
  151. */
  152. int DownhillSimplexOptimizer::amoeba()
  153. {
  154. //define the desired tolerance of our solution
  155. double tol = m_funcTol;
  156. //how many parameters do we optimize?
  157. const int ndim=m_numberOfParameters;
  158. if (m_verbose)
  159. {
  160. std::cerr << "ndim: " << ndim << std::endl;
  161. }
  162. // number of vertices
  163. const int spts=ndim+1;
  164. int i,j, max_val;
  165. // index of worst (lowest) vertex-point
  166. int ilo;
  167. // index of best (hightest) vertex point
  168. int ihi;
  169. // index of second worst (next hightest) vertex point
  170. int inhi;
  171. double rtol,ytry;
  172. // buffer to save a function value
  173. double ysave;
  174. matrix_type psum(1,ndim);
  175. //start time criteria
  176. m_startTime = clock();
  177. // get sum of vertex-coordinates
  178. for (j=0;j<ndim;j++)
  179. {
  180. double sum(0.0);
  181. for (i=0;i<spts;i++)
  182. sum += m_vertices(j,i);
  183. psum(0,j)=sum;
  184. }
  185. if (m_verbose)
  186. {
  187. std::cerr << "initial psum: ";
  188. for (j=0;j<ndim;j++)
  189. {
  190. std::cerr << psum(0,j) << " " ;
  191. }
  192. std::cerr << std::endl;
  193. }
  194. // loop until terminating
  195. //this is our main loop for the whole optimization
  196. for (;;)
  197. {
  198. //what is our current solution?
  199. if(m_verbose)
  200. {
  201. for(int u = 0; u < ndim+1; u++)
  202. {
  203. for(int v = 0; v < ndim ; v++)
  204. {
  205. std::cerr << m_vertices(v,u) << " ";
  206. }
  207. std::cerr<< " " << m_y(u,0) << std::endl;
  208. }
  209. std::cerr << std::endl;
  210. }
  211. // first we must determine
  212. // which point is highest,
  213. // next-highest
  214. // and lowest
  215. // by looping over the simplex
  216. ilo = 0;
  217. //this works only, if m_numberOfParameters=ndim >= 2
  218. if(ndim >= 2)
  219. {
  220. ihi = m_y(1,0)>m_y(2,0) ? (inhi=1,2) : (inhi=2,1);
  221. for (i=0;i<spts;i++)
  222. {
  223. if (m_y(i,0) <= m_y(ilo,0)) ilo=i;
  224. if (m_y(i,0) > m_y(ihi,0)) {
  225. inhi=ihi;
  226. ihi=i;
  227. }
  228. else
  229. if ( m_y(i,0) > m_y(inhi,0) )
  230. if (i != ihi)
  231. inhi=i;
  232. }
  233. }
  234. //if we have less than 3 dimensions, the solution is straight forward
  235. else
  236. {
  237. if(m_y(0,0)>m_y(1,0))
  238. {
  239. ilo = 1;
  240. inhi = 1;
  241. ihi = 0;
  242. }
  243. else
  244. {
  245. ilo = 0;
  246. inhi = 0;
  247. ihi = 1;
  248. }
  249. }
  250. // log parameters if parameter logger is given (does nothing for other loggers!)
  251. if(m_loger)
  252. {
  253. // extract optimal parameters from simplex
  254. matrix_type optparams(m_vertices.rows(),1,0);
  255. for(int i= 0; i< (int)m_vertices.rows(); ++i)
  256. {
  257. optparams(i,0)= m_vertices(i,ilo);
  258. }
  259. matrix_type fullparams= m_costFunction->getFullParamsFromSubParams(optparams);
  260. m_loger->writeParamsToFile(fullparams);
  261. }
  262. //Check m_abort .. outofbounds may have occurred in amotry() of last iteration
  263. if(m_abort == true)
  264. {
  265. break;
  266. }
  267. //Check time criterion
  268. //in the default setting, this is deactivated
  269. if(m_maxSecondsActive)
  270. {
  271. m_currentTime = clock();
  272. /* time limit exceeded ? */
  273. if(((float)(m_currentTime - m_startTime )/CLOCKS_PER_SEC) >= m_maxSeconds )
  274. {
  275. /* set according return status and end optimization */
  276. m_returnReason = SUCCESS_TIMELIMIT;
  277. break;
  278. }
  279. }
  280. //check functol criterion
  281. if(m_funcTolActive == true)
  282. {
  283. // compute the fractional
  284. // range from highest to lowest
  285. // avoid division by zero
  286. rtol=2.0*fabs(m_y(ihi,0)-m_y(ilo,0))/(fabs(m_y(ihi,0))+fabs(m_y(ilo,0))+1e-10);
  287. #ifdef OPT_DEBUG
  288. std::cout<<"rtol"<<" "<<rtol<< std::endl;
  289. #endif
  290. // if the found solution is satisfactory, than terminate
  291. if (rtol<tol)
  292. {
  293. // save lowest value, which is our desired solution
  294. max_val=(int)m_y(ilo,0);
  295. m_returnReason = SUCCESS_FUNCTOL;
  296. //break the main loop and end this method
  297. break;
  298. }
  299. }
  300. //check param tol criterion
  301. if (m_paramTolActive == true)
  302. {
  303. // get norm of
  304. //matrix_type tmp = m_vertices(0,ihi,m_numberOfParameters-1,ihi) - m_vertices(0,ilo,m_numberOfParameters,ilo);
  305. //double norm)
  306. if ( (m_vertices(0,ihi,m_numberOfParameters-1,ihi) -
  307. m_vertices(0,ilo,m_numberOfParameters-1,ilo)).frobeniusNorm() < m_paramTol)
  308. {
  309. /*
  310. set according return reason and end optimization
  311. */
  312. m_returnReason = SUCCESS_PARAMTOL;
  313. break;
  314. }
  315. }
  316. m_numIter++;
  317. //check max num iter criterion
  318. if(m_maxNumIterActive == true)
  319. {
  320. if (m_numIter >= m_maxNumIter)
  321. {
  322. //max_val=(int)m_y[ilo][0];
  323. m_returnReason = SUCCESS_MAXITER;
  324. break;
  325. }
  326. }
  327. //informative output
  328. if (m_verbose)
  329. {
  330. std::cerr << "start new iteration with amotry -alpha, i.e., reflect worst point through simplex" << std::endl;
  331. }
  332. // Begin a new iteration.
  333. // First extrapolate by the
  334. // factor alpha through the
  335. // face of the simplex across
  336. // from the high point, i.e.
  337. // reflect the simplex from the
  338. // high point:
  339. ytry=amotry(psum,ihi,-m_alpha);
  340. //did we got a new point better than anything else so far?
  341. if ( ytry < m_y(ilo,0) )
  342. {
  343. //informative output
  344. if (m_verbose)
  345. {
  346. std::cerr << "reflected point is better than best point, perform further extrapolation with gamma" << std::endl;
  347. }
  348. // result is better than best
  349. // point, try additional
  350. // extrapolation
  351. ytry=amotry(psum,ihi,m_gamma);
  352. #ifdef OPT_DEBUG
  353. std::cout<<"Case one .. reflected highest through simplex" << std::endl;
  354. #endif
  355. }
  356. //new point is not better as anything else
  357. else
  358. {
  359. //perhaps it is better than our second worst point
  360. if ( ytry >= m_y(inhi,0) )
  361. {
  362. //informative output
  363. if (m_verbose)
  364. {
  365. std::cerr << "reflected point is worse then second worst, looking for intermediate point with beta" << std::endl;
  366. }
  367. // The reflected point is worse
  368. // than the second worst
  369. ysave=m_y(ihi,0);
  370. // let's look for an intermediate lower point.
  371. ytry=amotry(psum,ihi,m_beta);
  372. #ifdef OPT_DEBUG
  373. std::cout<<"Case two .. looking for intermediate point" << std::endl;
  374. #endif
  375. //unfortunately, the intermediate point is still worse
  376. //then the original one
  377. if (ytry >= ysave)
  378. {
  379. #ifdef OPT_DEBUG
  380. std::cout<<"Case three .. contract around lowest point" << std::endl;
  381. #endif
  382. //informative output
  383. if (m_verbose)
  384. {
  385. std::cerr << "Intermediate point is also worse, contract around current best point with factor 0.5." << std::endl;
  386. }
  387. // Since we can't get rid
  388. // of that bad point, we better
  389. // our simplex around the current best point
  390. // note: contraction is performed by a factor of 0.5
  391. // as suggested in Numerical Recipes
  392. //contract all points
  393. for (i=0;i<spts;i++)
  394. {
  395. // but the best point has not to be contracted
  396. if (i!=ilo)
  397. {
  398. //contract in every dimension
  399. for (j=0;j<ndim;j++)
  400. {
  401. psum(0,j)=0.5*(m_vertices(j,i)+m_vertices(j,ilo) );
  402. #ifdef OPT_DEBUG
  403. printf( "psum(%d)=%f\n",j,psum(0,j) );
  404. #endif
  405. m_vertices(j,i)=psum(0,j);
  406. }
  407. //TODO what does this?
  408. if (checkParameters(!psum))
  409. {
  410. m_y(i,0)= evaluateCostFunction(!psum);
  411. // count function evaluations
  412. //not needed in the current implementation
  413. //eval_count++;
  414. }
  415. else
  416. {
  417. m_returnReason = ERROR_XOUTOFBOUNDS; // out of domain !!!
  418. break;
  419. }
  420. }
  421. // update psum: get sum of vertex-coordinates
  422. for (j=0;j<ndim;j++)
  423. {
  424. double sum=0.0;
  425. for (int ii=0;ii<spts;ii++)
  426. sum += m_vertices(j,ii);
  427. psum(0,j)=sum;
  428. }//for
  429. }//for-loop for contracting all points
  430. }//if (ytry >= ysave)
  431. }//if (ytry >= m_y(inhi))
  432. }//else
  433. }// next iteration
  434. // finally, return index of best point
  435. return ilo;
  436. }
  437. double DownhillSimplexOptimizer::amotry(matrix_type & psum, int ihi, double fac)
  438. {
  439. // extrapolate by a factor fac through the face of the simplex across
  440. // from the low point, try this point it, and replace the low point if
  441. // the new point is better
  442. const double maxreal= (m_maximize == true)? -1.0e+300 : 1.0e+300;
  443. double fac1,fac2,ytry;
  444. int ndim=m_numberOfParameters;
  445. matrix_type ptry(1,ndim);
  446. //compute the factors as suggested in Numerical Recipes
  447. fac1=(1.0-fac)/ndim;
  448. fac2=fac1-fac;
  449. //compute the new point by performing a weighted superposition of the previous point and the surface of our simplex
  450. for (int j=0;j<ndim;j++)
  451. {
  452. ptry(0,j) = psum(0,j)*fac1-m_vertices(j,ihi)*fac2;
  453. }
  454. //informative output
  455. if (m_verbose)
  456. {
  457. std::cerr << "amotry fac: " << fac << std::endl;
  458. std::cerr << "fac1: " << fac1 << " fac2: " << fac2 << std::endl;
  459. std::cerr << "ptry: ";
  460. for (int j=0;j<ndim;j++)
  461. {
  462. std::cerr << ptry(0,j) << " ";
  463. }
  464. std::cerr << std::endl;
  465. }
  466. //is the new point valid?
  467. if (checkParameters(!ptry))
  468. {
  469. // evaluate function at the
  470. // new trial point
  471. ytry=evaluateCostFunction(!ptry);
  472. // count function evaluations
  473. //not needed in the current implementation
  474. // eval_count++;
  475. // if the new point is better than the old one
  476. // we replace the old one with the new point
  477. if ( ytry<m_y(ihi,0) )
  478. {
  479. //save function value of the new point
  480. m_y(ihi,0) = ytry;
  481. //update the surface of our simplex
  482. for (int j=0; j<ndim;j++)
  483. {
  484. // update psum
  485. psum(0,j) = psum(0,j) + ptry(0,j)-m_vertices(j,ihi);
  486. // replace lowest point
  487. m_vertices(j,ihi) = ptry(0,j);
  488. }
  489. }
  490. }
  491. // out of domain
  492. else
  493. {
  494. ytry=maxreal;
  495. m_abort = true;
  496. m_returnReason = ERROR_XOUTOFBOUNDS;
  497. }
  498. return ytry;
  499. }
  500. void DownhillSimplexOptimizer::setDownhillParams(const double alpha, const double beta, const double gamma)
  501. {
  502. m_alpha = alpha;
  503. m_beta = beta;
  504. m_gamma = gamma;
  505. }
  506. void DownhillSimplexOptimizer::setRankDeficiencyThresh(float rankdeficiencythresh)
  507. {
  508. m_rankdeficiencythresh= rankdeficiencythresh;
  509. }
  510. void DownhillSimplexOptimizer::setRankCheckStatus(bool status)
  511. {
  512. m_rankcheckenabled= status;
  513. }
  514. double DownhillSimplexOptimizer::getDownhillParameterAlpha() const
  515. {
  516. return m_alpha;
  517. }
  518. double DownhillSimplexOptimizer::getDownhillParameterBeta() const
  519. {
  520. return m_beta;
  521. }
  522. double DownhillSimplexOptimizer::getDownhillParameterGamma() const
  523. {
  524. return m_gamma;
  525. }
  526. bool DownhillSimplexOptimizer::getRankCheckStatus()
  527. {
  528. return m_rankcheckenabled;
  529. }
  530. //only works with ICE and the method SingularValueDcmp
  531. // unsigned int getRank(const matrix_type &A,double numZero)
  532. // {
  533. // unsigned int tmpCount = 0;
  534. // matrix_type U,s,Vt;
  535. //
  536. // if(A.rows() < A.cols())
  537. // {
  538. // // call of the singular value decomp.
  539. // SingularValueDcmp(!A, U, s, Vt);
  540. // }
  541. // else
  542. // {
  543. // // call of the singular value decomp.
  544. // SingularValueDcmp(A, U, s, Vt);
  545. // }
  546. //
  547. // // count singular values > numZero
  548. // for(int i= 0; i < s.rows();i++)
  549. // {
  550. // if( s[i][i] > numZero )
  551. // {
  552. // tmpCount++;
  553. // }
  554. // }
  555. //
  556. // return tmpCount;
  557. // }