src/work/akos/simann.h
author ladanyi
Tue, 05 Apr 2005 22:37:19 +0000
changeset 1308 0274efa2222f
parent 1145 99c1aa395a58
permissions -rw-r--r--
Applied the changes which somehow vanished during my last merge. Thanks goes
to Marci for noticing this. In detail:
- added amsmath and amssymb latex packages for latex documentation
- src/demo is also scanned for doxygen input files
ladanyi@942
     1
#ifndef LEMON_SIMANN_H
ladanyi@942
     2
#define LEMON_SIMANN_H
ladanyi@918
     3
ladanyi@1142
     4
/// \ingroup experimental
ladanyi@1142
     5
/// \file
ladanyi@1142
     6
/// \brief Simulated annealing framework.
ladanyi@1142
     7
/// \author Akos Ladanyi
ladanyi@1142
     8
ladanyi@966
     9
#include <cstdlib>
ladanyi@966
    10
#include <cmath>
ladanyi@1018
    11
#include <lemon/time_measure.h>
ladanyi@966
    12
ladanyi@942
    13
namespace lemon {
ladanyi@918
    14
ladanyi@1142
    15
/// \addtogroup experimental
ladanyi@1142
    16
/// @{
ladanyi@1142
    17
ladanyi@942
    18
  const double INFTY = 1e24;
ladanyi@918
    19
ladanyi@1142
    20
  /*! \brief Simulated annealing base class. */
ladanyi@942
    21
  class SimAnnBase {
ladanyi@918
    22
  public:
ladanyi@942
    23
    class Controller;
ladanyi@942
    24
  private:
ladanyi@1142
    25
    /*! Pointer to the controller. */
ladanyi@942
    26
    Controller *controller;
ladanyi@942
    27
  protected:
ladanyi@1142
    28
    /*! \brief Cost of the current solution. */
ladanyi@942
    29
    double curr_cost;
ladanyi@1142
    30
    /*! \brief Cost of the best solution. */
ladanyi@1023
    31
    double best_cost;
ladanyi@1142
    32
    /*! \brief Cost of the previous solution. */
ladanyi@942
    33
    double prev_cost;
ladanyi@1142
    34
    /*! \brief Cost of the solution preceding the previous one. */
ladanyi@1023
    35
    double prev_prev_cost;
ladanyi@918
    36
ladanyi@1142
    37
    /*! \brief Step to a neighbouring state. */
alpar@1150
    38
    virtual void mutate() = 0;
ladanyi@1142
    39
    /*! \brief Reverts the last mutate(). */
alpar@1150
    40
    virtual void revert() = 0;
ladanyi@1142
    41
    /*! \brief Saves the current solution as the best one. */
alpar@1150
    42
    virtual void saveAsBest() = 0;
ladanyi@942
    43
  public:
ladanyi@1142
    44
    /*! \brief Constructor. */
ladanyi@942
    45
    SimAnnBase() {
ladanyi@1023
    46
      best_cost = prev_cost = prev_prev_cost = INFTY;
ladanyi@942
    47
    }
ladanyi@1142
    48
    /*! \brief Sets the controller class to use. */
ladanyi@957
    49
    void setController(Controller &_controller) {
ladanyi@957
    50
      controller = &_controller;
ladanyi@957
    51
      controller->setBase(this);
ladanyi@957
    52
    }
ladanyi@1142
    53
    /*! \brief Returns the cost of the current solution. */
ladanyi@1018
    54
    double getCurrCost() const { return curr_cost; }
ladanyi@1142
    55
    /*! \brief Returns the cost of the previous solution. */
ladanyi@1018
    56
    double getPrevCost() const { return prev_cost; }
ladanyi@1142
    57
    /*! \brief Returns the cost of the best solution. */
ladanyi@1018
    58
    double getBestCost() const { return best_cost; }
ladanyi@1142
    59
    /*! \brief Starts the annealing process. */
ladanyi@942
    60
    void run() {
ladanyi@966
    61
      controller->init();
ladanyi@1018
    62
      do {
alpar@1150
    63
        curr_cost=mutate();
ladanyi@957
    64
        if (controller->accept()) {
ladanyi@942
    65
          controller->acceptEvent();
ladanyi@942
    66
          if (curr_cost < best_cost) {
ladanyi@942
    67
            saveAsBest();
ladanyi@942
    68
            controller->improveEvent();
ladanyi@942
    69
          }
ladanyi@942
    70
        }
ladanyi@942
    71
        else {
ladanyi@942
    72
          revert();
ladanyi@942
    73
          controller->rejectEvent();
ladanyi@942
    74
        }
ladanyi@1018
    75
      } while (controller->next());
ladanyi@918
    76
    }
ladanyi@918
    77
ladanyi@1000
    78
    /*! \brief A base class for controllers. */
ladanyi@942
    79
    class Controller {
ladanyi@942
    80
    public:
ladanyi@1142
    81
      /*! \brief Pointer to the simulated annealing base class. */
ladanyi@957
    82
      SimAnnBase *base;
ladanyi@1142
    83
      /*! \brief Initializes the controller. */
ladanyi@966
    84
      virtual void init() {}
ladanyi@1000
    85
      /*! \brief This is called when a neighbouring state gets accepted. */
ladanyi@942
    86
      virtual void acceptEvent() {}
ladanyi@1000
    87
      /*! \brief This is called when the accepted neighbouring state's cost is
ladanyi@1000
    88
       *  less than the best found one's.
ladanyi@1000
    89
       */
ladanyi@942
    90
      virtual void improveEvent() {}
ladanyi@1000
    91
      /*! \brief This is called when a neighbouring state gets rejected. */
ladanyi@942
    92
      virtual void rejectEvent() {}
ladanyi@1142
    93
      /*! \brief Sets the simulated annealing base class to use. */
ladanyi@957
    94
      virtual void setBase(SimAnnBase *_base) { base = _base; }
ladanyi@1142
    95
      /*! \brief Decides whether to continue the annealing process or not. */
ladanyi@942
    96
      virtual bool next() = 0;
ladanyi@1142
    97
      /*! \brief Decides whether to accept the current solution or not. */
ladanyi@957
    98
      virtual bool accept() = 0;
ladanyi@942
    99
    };
ladanyi@942
   100
  };
ladanyi@918
   101
ladanyi@1142
   102
  /*! \brief Simulated annealing class. */
ladanyi@942
   103
  template <typename E>
ladanyi@942
   104
  class SimAnn : public SimAnnBase {
ladanyi@942
   105
  private:
ladanyi@1142
   106
    /*! \brief Pointer to the current entity. */
ladanyi@942
   107
    E *curr_ent;
ladanyi@1142
   108
    /*! \brief Pointer to the best entity. */
ladanyi@942
   109
    E *best_ent;
ladanyi@942
   110
  public:
ladanyi@1142
   111
    /*! \brief Constructor. */
ladanyi@957
   112
    SimAnn() : SimAnnBase() {}
ladanyi@1142
   113
    /*! \brief Sets the initial entity. */
ladanyi@957
   114
    void setEntity(E &ent) {
ladanyi@957
   115
      curr_ent = new E(ent);
ladanyi@957
   116
      best_ent = new E(ent);
ladanyi@1023
   117
      curr_cost = curr_ent->getCost();
ladanyi@942
   118
    }
ladanyi@1142
   119
    /*! \brief Returns the best found entity. */
ladanyi@942
   120
    E getBestEntity() { return *best_ent; }
ladanyi@1142
   121
    /*! \brief Step to a neighbouring state. */
ladanyi@942
   122
    void mutate() {
ladanyi@1023
   123
      prev_prev_cost = prev_cost;
ladanyi@1018
   124
      prev_cost = curr_cost;
ladanyi@1023
   125
      curr_ent->mutate();
ladanyi@1023
   126
      curr_cost = curr_ent->getCost();
ladanyi@942
   127
    }
ladanyi@1142
   128
    /*! \brief Reverts the last mutate(). */
ladanyi@942
   129
    void revert() {
ladanyi@942
   130
      curr_ent->revert();
ladanyi@1018
   131
      curr_cost = prev_cost;
ladanyi@1023
   132
      prev_cost = prev_prev_cost;
ladanyi@942
   133
    }
ladanyi@1142
   134
    /*! \brief Saves the current solution as the best one. */
ladanyi@942
   135
    void saveAsBest() {
ladanyi@1096
   136
      delete(best_ent);
ladanyi@1096
   137
      best_ent = new E(*curr_ent);
ladanyi@942
   138
      best_cost = curr_cost;
ladanyi@942
   139
    }
ladanyi@942
   140
  };
ladanyi@942
   141
ladanyi@1142
   142
  /*! \brief Skeleton of an entity class. */
ladanyi@956
   143
  class EntitySkeleton {
ladanyi@942
   144
  public:
ladanyi@1142
   145
    /*! \brief Returns the cost of the entity. */
ladanyi@1023
   146
    double getCost() { return 0.0; }
ladanyi@1023
   147
    /*! \brief Makes a minor change to the entity. */
ladanyi@1023
   148
    void mutate() {}
ladanyi@966
   149
    /*! \brief Restores the entity to its previous state i.e. reverts the
ladanyi@1142
   150
     *  effects of the last mutate().
ladanyi@966
   151
     */
ladanyi@942
   152
    void revert() {}
ladanyi@942
   153
  };
ladanyi@942
   154
ladanyi@1142
   155
  /*! \brief A simple controller for the simulated annealing class. */
ladanyi@956
   156
  class SimpleController : public SimAnnBase::Controller {
ladanyi@956
   157
  public:
ladanyi@1142
   158
    /*! \brief Number of iterations. */
ladanyi@1142
   159
    long iter;
ladanyi@1142
   160
    /*! \brief Number of iterations which did not improve the solution since
ladanyi@1142
   161
     *  the last improvement. */
ladanyi@1142
   162
    long last_impr;
ladanyi@1142
   163
    /*! \brief Maximum number of iterations. */
ladanyi@1142
   164
    long max_iter;
ladanyi@1142
   165
    /*! \brief Maximum number of iterations which do not improve the
ladanyi@1142
   166
     *  solution. */
ladanyi@1142
   167
    long max_no_impr;
ladanyi@1142
   168
    /*! \brief Temperature. */
ladanyi@1142
   169
    double temp;
ladanyi@1142
   170
    /*! \brief Annealing factor. */
ladanyi@1142
   171
    double ann_fact;
ladanyi@1142
   172
    /*! \brief Constructor.
ladanyi@1142
   173
     *  \param _max_iter maximum number of iterations
ladanyi@1000
   174
     *  \param _max_no_impr maximum number of consecutive iterations which do
ladanyi@1000
   175
     *         not yield a better solution
ladanyi@1000
   176
     *  \param _temp initial temperature
ladanyi@1000
   177
     *  \param _ann_fact annealing factor
ladanyi@1000
   178
     */
ladanyi@1000
   179
    SimpleController(long _max_iter = 500000, long _max_no_impr = 20000,
ladanyi@1096
   180
    double _temp = 1000.0, double _ann_fact = 0.9999) : iter(0), last_impr(0),
ladanyi@1000
   181
    max_iter(_max_iter), max_no_impr(_max_no_impr), temp(_temp),
ladanyi@1000
   182
    ann_fact(_ann_fact) {}
ladanyi@1145
   183
    /*! \brief This is called when a neighbouring state gets accepted. */
ladanyi@956
   184
    void acceptEvent() {
ladanyi@956
   185
      iter++;
ladanyi@956
   186
    }
ladanyi@1142
   187
    /*! \brief This is called when the accepted neighbouring state's cost is
ladanyi@1142
   188
     *  less than the best found one's.
ladanyi@1142
   189
     */
ladanyi@956
   190
    void improveEvent() {
ladanyi@956
   191
      last_impr = iter;
ladanyi@956
   192
    }
ladanyi@1142
   193
    /*! \brief This is called when a neighbouring state gets rejected. */
ladanyi@956
   194
    void rejectEvent() {
ladanyi@956
   195
      iter++;
ladanyi@956
   196
    }
ladanyi@1142
   197
    /*! \brief Decides whether to continue the annealing process or not. Also
ladanyi@1142
   198
     *  decreases the temperature. */
ladanyi@956
   199
    bool next() {
ladanyi@1000
   200
      temp *= ann_fact;
ladanyi@956
   201
      bool quit = (iter > max_iter) || (iter - last_impr > max_no_impr);
ladanyi@956
   202
      return !quit;
ladanyi@956
   203
    }
ladanyi@1142
   204
    /*! \brief Decides whether to accept the current solution or not. */
ladanyi@957
   205
    bool accept() {
ladanyi@1018
   206
      double cost_diff = base->getPrevCost() - base->getCurrCost();
ladanyi@1018
   207
      if (cost_diff < 0.0) {
ladanyi@1096
   208
        bool ret = drand48() <= exp(cost_diff / temp);
ladanyi@1096
   209
        return ret;
ladanyi@1018
   210
      }
ladanyi@1018
   211
      else {
ladanyi@1018
   212
        return true;
ladanyi@1018
   213
      }
ladanyi@966
   214
    }
ladanyi@966
   215
  };
ladanyi@966
   216
ladanyi@966
   217
  /*! \brief A controller with preset running time for the simulated annealing
ladanyi@966
   218
   *  class.
ladanyi@1145
   219
   *
ladanyi@1145
   220
   *  With this controller you can set the running time of the annealing
ladanyi@1145
   221
   *  process in advance. It works the following way: the controller measures
ladanyi@1145
   222
   *  a kind of divergence. The divergence is the difference of the average
ladanyi@1145
   223
   *  cost of the recently found solutions the cost of the best found one. In
ladanyi@1145
   224
   *  case this divergence is greater than a given threshold, then we decrease
ladanyi@1145
   225
   *  the annealing factor, that is we cool the system faster. In case the
ladanyi@1145
   226
   *  divergence is lower than the threshold, then we increase the temperature.
ladanyi@1145
   227
   *  The threshold is a function of the elapsed time which reaches zero at the
ladanyi@1145
   228
   *  desired end time.
ladanyi@966
   229
   */
ladanyi@966
   230
  class AdvancedController : public SimAnnBase::Controller {
ladanyi@966
   231
  private:
ladanyi@1018
   232
    Timer timer;
ladanyi@1000
   233
    /*! \param time the elapsed time in seconds */
ladanyi@1018
   234
    virtual double threshold(double time) {
ladanyi@1096
   235
      return (-1.0) * start_threshold / end_time * time + start_threshold;
ladanyi@1018
   236
    }
ladanyi@966
   237
  public:
ladanyi@1142
   238
    double alpha;
ladanyi@1142
   239
    double beta;
ladanyi@1142
   240
    double gamma;
ladanyi@1145
   241
    /*! \brief The time at the end of the algorithm. */
ladanyi@1142
   242
    double end_time;
ladanyi@1145
   243
    /*! \brief The time at the start of the algorithm. */
ladanyi@1142
   244
    double start_time;
ladanyi@1145
   245
    /*! \brief Starting threshold. */
ladanyi@1018
   246
    double start_threshold;
ladanyi@1145
   247
    /*! \brief Average cost of recent solutions. */
ladanyi@966
   248
    double avg_cost;
ladanyi@1145
   249
    /*! \brief Temperature. */
ladanyi@1142
   250
    double temp;
ladanyi@1145
   251
    /*! \brief Annealing factor. */
ladanyi@1142
   252
    double ann_fact;
ladanyi@1145
   253
    /*! \brief Initial annealing factor. */
ladanyi@1145
   254
    double init_ann_fact;
ladanyi@1018
   255
    bool warmup;
ladanyi@1142
   256
    /*! \brief Constructor.
ladanyi@1142
   257
     *  \param _end_time running time in seconds
ladanyi@1000
   258
     *  \param _alpha parameter used to calculate the running average
ladanyi@1000
   259
     *  \param _beta parameter used to decrease the annealing factor
ladanyi@1000
   260
     *  \param _gamma parameter used to increase the temperature
ladanyi@1145
   261
     *  \param _ann_fact initial annealing factor
ladanyi@1000
   262
     */
ladanyi@1000
   263
    AdvancedController(double _end_time, double _alpha = 0.2,
ladanyi@1145
   264
    double _beta = 0.9, double _gamma = 1.6, double _ann_fact = 0.9999) :
ladanyi@1145
   265
    alpha(_alpha), beta(_beta), gamma(_gamma), end_time(_end_time),
ladanyi@1145
   266
    ann_fact(_ann_fact), init_ann_fact(_ann_fact), warmup(true) {}
ladanyi@966
   267
    void init() {
ladanyi@1018
   268
      avg_cost = base->getCurrCost();
ladanyi@966
   269
    }
ladanyi@1142
   270
    /*! \brief This is called when a neighbouring state gets accepted. */
ladanyi@966
   271
    void acceptEvent() {
ladanyi@966
   272
      avg_cost = alpha * base->getCurrCost() + (1.0 - alpha) * avg_cost;
ladanyi@1023
   273
      if (warmup) {
ladanyi@1096
   274
        static int cnt = 0;
ladanyi@1096
   275
        cnt++;
ladanyi@1096
   276
        if (cnt >= 100) {
ladanyi@1023
   277
          // calculate starting threshold and starting temperature
ladanyi@1096
   278
          start_threshold = 5.0 * fabs(base->getBestCost() - avg_cost);
ladanyi@1096
   279
          temp = 10000.0;
ladanyi@1023
   280
          warmup = false;
ladanyi@1023
   281
          timer.reset();
ladanyi@1023
   282
        }
ladanyi@1023
   283
      }
ladanyi@966
   284
    }
ladanyi@1142
   285
    /*! \brief Decides whether to continue the annealing process or not. */
ladanyi@966
   286
    bool next() {
ladanyi@1018
   287
      if (warmup) {
ladanyi@1018
   288
        return true;
ladanyi@1000
   289
      }
ladanyi@1000
   290
      else {
ladanyi@1018
   291
        double elapsed_time = timer.getRealTime();
ladanyi@1018
   292
        if (fabs(avg_cost - base->getBestCost()) > threshold(elapsed_time)) {
ladanyi@1018
   293
          // decrease the annealing factor
ladanyi@1018
   294
          ann_fact *= beta;
ladanyi@1018
   295
        }
ladanyi@1018
   296
        else {
ladanyi@1018
   297
          // increase the temperature
ladanyi@1018
   298
          temp *= gamma;
ladanyi@1145
   299
          // reset the annealing factor
ladanyi@1145
   300
          ann_fact = init_ann_fact;
ladanyi@1018
   301
        }
ladanyi@1018
   302
        temp *= ann_fact;
ladanyi@1018
   303
        return elapsed_time < end_time;
ladanyi@1000
   304
      }
ladanyi@966
   305
    }
ladanyi@1142
   306
    /*! \brief Decides whether to accept the current solution or not. */
ladanyi@966
   307
    bool accept() {
ladanyi@1018
   308
      if (warmup) {
ladanyi@1018
   309
        // we accept eveything during the "warm up" phase
ladanyi@1018
   310
        return true;
ladanyi@1018
   311
      }
ladanyi@1018
   312
      else {
ladanyi@1018
   313
        double cost_diff = base->getPrevCost() - base->getCurrCost();
ladanyi@1018
   314
        if (cost_diff < 0.0) {
ladanyi@1018
   315
          return (drand48() <= exp(cost_diff / temp));
ladanyi@1018
   316
        }
ladanyi@1018
   317
        else {
ladanyi@1018
   318
          return true;
ladanyi@1018
   319
        }
ladanyi@1018
   320
      }
ladanyi@956
   321
    }
ladanyi@956
   322
  };
ladanyi@956
   323
ladanyi@1142
   324
/// @}
ladanyi@1142
   325
ladanyi@942
   326
}
ladanyi@918
   327
ladanyi@918
   328
#endif