homer512

Decimate vector

Dec 2nd, 2013
251
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 11.07 KB | None | 0 0
  1. /*
  2.  * Copyright 2013 Florian Philipp
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  * http://www.apache.org/licenses/LICENSE-2.0
  9.  
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. #include <cstdlib>
  17. // using std::rand, RAND_MAX, EXIT_SUCCESS
  18. #include <vector>
  19. // using std::vector
  20. #include <queue>
  21. // using std::priority_queue
  22. #include <array>
  23. // using std::array
  24. #include <algorithm>
  25. // using std::sort, std::for_each, std::make_heap, std::pop_heap, std::remove_if
  26. #include <functional>
  27. // using std::greater
  28. #include <cstdio>
  29. // using stdout, stderr, std::FILE, std::fprintf
  30. #include <chrono>
  31. // using std::chrono::high_resolution_clock, std::chrono::duration,
  32. // using std::chrono::duration_cast, std::chrono::milliseconds
  33. #include <stdexcept>
  34. // using std::logic_error
  35. #include <utility>
  36. // using std::swap
  37.  
  38. namespace {
  39.   typedef std::chrono::high_resolution_clock clock_type;
  40.  
  41.   /**
  42.    * Mockup of a candidate solution in a genetic algorithm
  43.    */
  44.   class Individuum
  45.   {
  46.     double fitness;
  47.     static double generate_fitness()
  48.     {
  49.       return static_cast<double>(std::rand())/RAND_MAX;
  50.     }
  51.   public:
  52.     Individuum():
  53.       fitness(0)
  54.     {}
  55.     void evolve()
  56.     {
  57.       fitness = generate_fitness();
  58.     }
  59.     bool operator<(const Individuum& o) const
  60.     {
  61.       return fitness < o.fitness;
  62.     }
  63.     bool operator<=(const Individuum& o)  const
  64.     {
  65.       return fitness <= o.fitness;
  66.     }
  67.     bool operator>(const Individuum& o) const
  68.     {
  69.       return o < *this;
  70.     }
  71.     bool operator>=(const Individuum& o) const
  72.     {
  73.       return o <= *this;
  74.     }
  75.     bool operator==(const Individuum& o) const
  76.     {
  77.       return fitness == o.fitness;
  78.     }
  79.     bool operator!=(const Individuum& o) const
  80.     {
  81.       return !(*this == o);
  82.     }
  83.     double get_fitness() const
  84.     {
  85.       return fitness;
  86.     }
  87.     void swap(Individuum& o)
  88.     {
  89.       using std::swap;
  90.       swap(fitness, o.fitness);
  91.     }
  92.   };
  93. } // namespace
  94. namespace std {
  95.   template<>
  96.   void swap<Individuum>(Individuum& a, Individuum& b)
  97.   {
  98.     a.swap(b);
  99.   }
  100. }
  101. namespace {
  102.   /**
  103.    * Function for use with std::for_each
  104.    */
  105.   void evolve(Individuum& i) { i.evolve(); }
  106.  
  107.   /**
  108.    * A heap of fitness values
  109.    *
  110.    * The top() element has the highest fitness
  111.    */
  112.   typedef std::priority_queue<double> heap_t;
  113.   /**
  114.    * Fills the heap given in the constructor with the n least fit Individuums
  115.    */
  116.   struct FillHeap
  117.   {
  118.     typedef heap_t::size_type size_type;
  119.     heap_t& least_fit_n;
  120.     const size_type capacity;
  121.     FillHeap(heap_t& least_fit_n, size_type n):
  122.       least_fit_n(least_fit_n), capacity(n)
  123.     {}
  124.     void operator()(Individuum& i)
  125.     {
  126.       double fitness = i.get_fitness();
  127.       if(least_fit_n.size() == capacity) {
  128.     if(fitness < least_fit_n.top()) {
  129.       least_fit_n.pop();
  130.       least_fit_n.push(fitness);
  131.     }
  132.       }
  133.       else
  134.     least_fit_n.push(fitness);
  135.     }
  136.   };
  137.   /**
  138.    * Unary functor that returns true for Individuums that are less or equal fit
  139.    * compared to the value given in the constructor
  140.    */
  141.   struct LessEqual
  142.   {
  143.     double comparison;
  144.     LessEqual(double comparison): comparison(comparison) {}
  145.     bool operator()(const Individuum& o) const
  146.     {
  147.       return o.get_fitness() <= comparison;
  148.     }
  149.   };
  150.   /**
  151.    * Similar to std::remove_if but does not preserve relative order of the
  152.    * remaining elements
  153.    *
  154.    * \tparam BidirectionalIterator an iterator supporting incrementing and
  155.    * decrementing. The type pointed to has to be move assignable
  156.    * \tparam Predicate a unary predicate which returns true for all elements
  157.    * that have to be removed
  158.    * \param first beginning of the range that is to be evaluated
  159.    * \param last iterator behind the end of the evaluated range
  160.    * \param predicate the evaluation functor
  161.    * \return an iterator pointing to the end of the range of remaining elements.
  162.    * Elements behind this point and last are in valid but unordered and
  163.    * undefined state. Elements between this point and first are in valid state
  164.    * but unsorted
  165.    */
  166.   template<class BidirectionalIterator, class Predicate>
  167.   BidirectionalIterator remove_unstable_if(BidirectionalIterator first,
  168.                        BidirectionalIterator last,
  169.                        Predicate predicate)
  170.   {
  171.     while(first != last) {
  172.       if(predicate(*first)) {
  173.     --last;
  174.     *first = std::move(*last);
  175.       }
  176.       else
  177.     ++first;
  178.     }
  179.     return last;
  180.   }
  181.   /**
  182.    * Storage of all candidate solutions of a genetic algorithm
  183.    *
  184.    * Implements various decimation strategies for benchmarking purposes
  185.    */
  186.   class Population
  187.   {
  188.     typedef std::vector<Individuum> population_t;
  189.   public:
  190.     typedef population_t::size_type size_type;
  191.   private:
  192.     population_t population;
  193.     size_type n_to_decimate(double percentage) const
  194.     {
  195.       return static_cast<size_type>(percentage * population.size());
  196.     }
  197.     size_type n_to_survive(double percentage_killoff) const
  198.     {
  199.       return population.size() - n_to_decimate(percentage_killoff);
  200.     }
  201.   public:
  202.     void evolve_all()
  203.     {
  204.       std::for_each(population.begin(), population.end(), evolve);
  205.     }
  206.     void grow_to(size_type n)
  207.     {
  208.       population.resize(n);
  209.     }
  210.     /**
  211.      * Removes the given percentage of Individuums by sorting the candidate
  212.      * solutions by descending fitness and then shrinking the container
  213.      *
  214.      * Usually the slowest approach unless the population is already sorted
  215.      */
  216.     void decimate_with_sort(double percentage)
  217.     {
  218.       std::sort(population.begin(), population.end(),
  219.         std::greater<Individuum>());
  220.       population.resize(n_to_survive(percentage));
  221.     }
  222.     /**
  223.      * Removes the given percentage of Individuums by reordering the candidate
  224.      * solutions as a heap, then popping elements from the heap and shrinking
  225.      * the container
  226.      *
  227.      * Usually the fastest approach
  228.      */
  229.     void decimate_with_heap(double percentage)
  230.     {
  231.       std::make_heap(population.begin(), population.end(),
  232.              std::greater<Individuum>());
  233.       const size_type n = n_to_survive(percentage);
  234.       const population_t::iterator new_end = population.begin() + n;
  235.       for(population_t::iterator cur_end = population.end(); cur_end != new_end;
  236.       --cur_end)
  237.     std::pop_heap(population.begin(), cur_end, std::greater<Individuum>());
  238.       population.erase(new_end, population.end());
  239.     }
  240.     /**
  241.      * Removes the given percentage of Individuums by finding the least fit
  242.      * percentage and then removing them in a second pass
  243.      *
  244.      * Slightly slower than decimate_with_heap. Can be faster with very low
  245.      * percentages
  246.      */
  247.     void decimate_with_twopass_heap(double percentage)
  248.     {
  249.       const size_type n = n_to_decimate(percentage);
  250.       if(n == 0)
  251.     return;
  252.       heap_t heap;
  253.       std::for_each(population.begin(), population.end(), FillHeap(heap, n));
  254.       double cutoff = heap.top();
  255.       population.erase(std::remove_if(population.begin(), population.end(),
  256.                       LessEqual(cutoff)),
  257.                population.end());
  258.     }
  259.     /**
  260.      * Equivalent to decimate_with_twopass_heap but uses remove_unstable_if
  261.      *
  262.      * Slightly faster than decimate_with_twopass_heap but probably
  263.      * insignificant
  264.      */
  265.     void decimate_with_twopass_unstable(double percentage)
  266.     {
  267.       const size_type n = n_to_decimate(percentage);
  268.       if(n == 0)
  269.     return;
  270.       heap_t heap;
  271.       std::for_each(population.begin(), population.end(), FillHeap(heap, n));
  272.       double cutoff = heap.top();
  273.       population.erase(remove_unstable_if(population.begin(), population.end(),
  274.                       LessEqual(cutoff)),
  275.                population.end());
  276.     }
  277.     void print(std::FILE* f) const
  278.     {
  279.       population_t::const_iterator i = population.begin();
  280.       const population_t::const_iterator end = population.end();
  281.       if(i != end) {
  282.     std::fprintf(f, "%4f", i->get_fitness());
  283.     for(++i; i != end; ++i)
  284.       std::fprintf(f, " %4f", i->get_fitness());
  285.       }
  286.       std::fprintf(f, "\n");
  287.     }
  288.     typedef void (Population::* decimation_fun_t)(double);
  289.     clock_type::duration benchmark(std::size_t iterations, double percentile,
  290.                    decimation_fun_t decimation_fun)
  291.     {
  292.       clock_type::duration total_time;
  293.       const size_type pop_count = population.size();
  294.       for(std::size_t i = 0; i < iterations; ++i) {
  295.     evolve_all();
  296.     clock_type::time_point start_time = clock_type::now();
  297.     (this->*decimation_fun)(percentile);
  298.     total_time += clock_type::now() - start_time;
  299.     grow_to(pop_count);
  300.       }
  301.       return total_time;
  302.     }
  303.     bool operator==(Population& o)
  304.     {
  305.       if(population.size() != o.population.size())
  306.     return false;
  307.       std::sort(population.begin(), population.end());
  308.       std::sort(o.population.begin(), o.population.end());
  309.       return std::equal(population.begin(), population.end(),
  310.             o.population.begin());
  311.     }
  312.     bool operator!=(Population& o)
  313.     {
  314.       return ! (*this == o);
  315.     }
  316.     void test()
  317.     {
  318.       grow_to(1000);
  319.       evolve_all();
  320.       std::array<Population, 3> copies;
  321.       copies.fill(*this);
  322.       const double percentage = 0.2;
  323.       this->decimate_with_sort(percentage);
  324.       copies[0].decimate_with_heap(percentage);
  325.       if(copies[0] != *this)
  326.     throw std::logic_error("Decimate with heap not equivalent");
  327.       copies[1].decimate_with_twopass_heap(percentage);
  328.       if(copies[1] != *this)
  329.     throw std::logic_error("Decimate with twopass heap not equivalent");
  330.       copies[2].decimate_with_twopass_unstable(percentage);
  331.       if(copies[2] != *this)
  332.     throw std::logic_error("Decimate with twopass unstable not equivalent");
  333. }
  334.   };
  335.   std::size_t to_ms(const clock_type::duration time)
  336.   {
  337.     return std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
  338.   }
  339.   void run_benchmark(Population& population,
  340.              Population::decimation_fun_t decimation_fun,
  341.              const char* fun_name)
  342.   {
  343.     const std::size_t iterations = 1000;
  344.     const Population::size_type pop_count = 100000;
  345.     const double percentage = 0.1;
  346.     population.grow_to(pop_count);
  347.     clock_type::duration time = population.benchmark(iterations, percentage,
  348.                              decimation_fun);
  349.     std::fprintf(stdout, "%s: %zd ms\n", fun_name, to_ms(time));
  350.   }
  351. } // namespace
  352.  
  353. int main()
  354. {
  355.   Population population;
  356.   population.test();
  357.   run_benchmark(population, &Population::decimate_with_sort, "Sort");
  358.   run_benchmark(population, &Population::decimate_with_heap, "Heap");
  359.   run_benchmark(population, &Population::decimate_with_twopass_heap, "Twopass");
  360.   run_benchmark(population, &Population::decimate_with_twopass_unstable,
  361.         "Twopass unstable");
  362.   return EXIT_SUCCESS;
  363. }
Advertisement
Add Comment
Please, Sign In to add comment