equifactor.h
Go to the documentation of this file.
1 /*
2 This file is part of the Ristra Wonton project.
3 Please see the license file at the root of this repository, or at:
4  https://github.com/laristra/wonton/blob/master/LICENSE
5 */
6 
7 #ifndef WONTON_EQUIFACTOR_H_
8 #define WONTON_EQUIFACTOR_H_
9 
10 #include <vector>
11 #include <algorithm>
12 #include <unordered_set>
13 #include <iostream>
14 #include <cmath>
15 #include <ctime>
16 #include <string>
17 
19 
42 namespace Wonton {
43 
44 #ifdef ENABLE_DEBUG
45 void print_sets(std::vector<std::vector<int>> sets);
46 #endif
47 
48 // gcc 7.3.0 doesn't recognize that this function is used in the code
49 // so use the compiler specific attribute to turn off the warning (since we
50 // use -Werror and cannot get past compilation)
51 #if defined(__GNUC__) && (__GNUC__ == 7 && __GNUC_MINOR__ == 3)
52 __attribute__ ((unused))
53 #endif
54 static
55 std::vector<int> equifactor(int const N, int const D, int const randseed = 0) {
56  clock_t startclock, curclock;
57  startclock = clock();
58 
59  assert(N > 0 && D > 0);
60 
61  if (D == 1)
62  return std::vector<int>(1, N);
63 
64  // Find all the prime factors of N (with duplicates)
65 
66  std::vector<int> prime_facs = prime_factors(N);
67  std::sort(prime_facs.begin(), prime_facs.end());
68  int const prime_facs_size = prime_facs.size();
69  if (prime_facs_size < D) {
70  for (int i = prime_facs_size; i < D; i++)
71  prime_facs.push_back(1);
72  }
73 
74 
75  std::vector<std::vector<int>> sets(D, {1}); // init to 1 for degenerate cases
76  std::vector<std::vector<int>> minsets;
77 
78  // Partition these factors into D sets in a simplistic manner.
79  // Since prime_facs is sorted, we are distributing small and large
80  // factors equally amongst the sets as much as possible
81 
82  int kset = 0;
83  for (int const n : prime_facs) {
84  sets[kset%D].push_back(n);
85  kset++;
86  }
87 
88 #ifdef ENABLE_DEBUG
89  // print out initial sets
90  std::cerr << "\n\nNumber of sets D " << D << "\n";
91  std::cerr << "INITIAL STATE:\n";
92  print_sets(sets);
93 #endif
94 
95 
96  std::vector<int> products(D);
97 
98  bool outer_done = false;
99  int maxiter = 100*D;
100  int outer_iter = 0;
101  int num_no_change = 0;
102  bool allow_swap_climb = false, allow_move_climb = false;
103  int nclimbs = 0;
104  int olddiff = 0;
105 
106  minsets = sets;
107 
108  if (randseed)
109  srand(randseed);
110 
111  while (!outer_done) {
112 
113  // compute the measure (product of elements of set) for each set
114  products.assign(D, 1.0);
115  for (int i = 0; i < D; i++)
116  for (int const n : sets[i])
117  products[i] *= n;
118 
119  // Min and max products
120  int minprod = *(std::min_element(products.begin(), products.end()));
121  int maxprod = *(std::max_element(products.begin(), products.end()));
122 
123  int diff = maxprod-minprod;
124  if (diff == 0) { // all sets have same measure
125  outer_done = true;
126  continue;
127  }
128 
129  // collect _all_ sets with min measure and _all_ sets with max measure
130  std::vector<int> iminsets, imaxsets;
131  for (int i = 0; i < D; i++) {
132  if (products[i] == minprod) iminsets.push_back(i);
133  if (products[i] == maxprod) imaxsets.push_back(i);
134  }
135 
136  // Find one random set from the minsets and one from the maxsets
137  if (!randseed) {
138  curclock = clock();
139  srand(curclock - startclock);
140  }
141 
142  int iset1 = iminsets[rand() % iminsets.size()];
143  int iset2 = imaxsets[rand() % imaxsets.size()];
144 
145  std::vector<int>& set1 = sets[iset1];
146  std::vector<int>& set2 = sets[iset2];
147  int & prod1 = products[iset1];
148  int & prod2 = products[iset2];
149 
150 
151  // swap/move elements between sets to try to reduce difference
152  int inner_iter = 0;
153  bool inner_done = false;
154  while (!inner_done) {
155 
156  if (!randseed) {
157  curclock = clock();
158  srand(curclock - startclock);
159  }
160 
161  int offset = rand() % set1.size();
162  auto j_set1 = set1.begin() + offset;
163  if (prod1 != 1) {
164  while (*j_set1 == 1) { // Search for value that's not 1
165  offset = rand() % set1.size();
166  j_set1 = set1.begin() + offset;
167  }
168  }
169  int& val_set1 = *j_set1;
170 
171  if (!randseed) {
172  curclock = clock();
173  srand(curclock - startclock);
174  }
175 
176  offset = rand() % set2.size();
177  auto j_set2 = set2.begin() + offset;
178  if (prod2 != 1) {
179  while (*j_set2 == 1) { // Search for value that's not 1
180  offset = rand() % set2.size();
181  j_set2 = set2.begin() + offset;
182  }
183  }
184  int& val_set2 = *j_set2;
185 
186  // will swapping the two result in a smaller difference in the
187  // set measures (product of the two members) or are we giving a
188  // random kick because we are stalled?
189 
190 
191  int prod1_new = val_set2*prod1/val_set1;
192  int prod2_new = val_set1*prod2/val_set2;
193 
194  int diff1 = prod2_new - prod1_new;
195 
196  if (std::abs(diff1) < std::abs(diff) || allow_swap_climb) {
197 
198  std::swap(val_set1, val_set2);
199  prod1 = prod1_new;
200  prod2 = prod2_new;
201  diff = diff1;
202 
203  allow_swap_climb = false;
204 
205  } else {
206 
207  // will moving an element of the max set to the min set reduce
208  // the difference or are we giving a random kick because we
209  // are stalled?
210 
211  prod1_new = prod1*val_set2;
212  prod2_new = prod2/val_set2;
213 
214  diff1 = prod2_new - prod1_new;
215 
216  if (std::abs(diff1) < std::abs(diff) || allow_move_climb) {
217 
218  set1.push_back(val_set2);
219  val_set2 = 1.0; // erase is expensive; do a "virtual deletion"
220  prod1 = prod1_new;
221  prod2 = prod2_new;
222  diff = diff1;
223 
224  allow_move_climb = false;
225  }
226  }
227 
228  inner_iter++;
229  if (unsigned(inner_iter) > 2*set1.size()) inner_done = true;
230  } // while (!inner_done)
231 
232  outer_iter++;
233 
234 
235  minprod = *(std::min_element(products.begin(), products.end()));
236  maxprod = *(std::max_element(products.begin(), products.end()));
237 
238  int maxdiff = maxprod-minprod;
239 
240  if (maxdiff == olddiff)
241  num_no_change++;
242  else {
243  num_no_change = 0;
244  if (maxdiff < olddiff)
245  minsets = sets; // record sets that minimized the min/max diff
246  olddiff = maxdiff;
247  }
248 
249  if (num_no_change > 5) { // stalled; allow obj. func. to increase
250  if (num_no_change%2)
251  allow_swap_climb = true;
252  else
253  allow_move_climb = true;
254  nclimbs++;
255  }
256 
257  if (nclimbs > 10) outer_done = true;
258  if (outer_iter > maxiter) outer_done = true;
259  } // while (!outer_done)
260 
261 #ifdef ENABLE_DEBUG
262  std::cerr << "\nFINAL STATE:\n";
263  std::vector<std::vector<int>> newsets(D);
264  for (int i = 0; i < D; i++) {
265  if (minsets[i].size() == 1)
266  newsets[i].push_back(minsets[i][0]);
267  else
268  for (int const n : minsets[i])
269  if (n != 1) newsets[i].push_back(n);
270  }
271  std::swap(minsets,newsets);
272  print_sets(minsets);
273 #endif
274 
275  products.assign(D, 1.0);
276  for (int i = 0; i < D; i++) {
277  products[i] = 1.0;
278  for (int const n : minsets[i])
279  products[i] *= n;
280  }
281 
282  return products;
283 } // equipartition
284 
285 
286 #ifdef ENABLE_DEBUG
287 void print_sets(std::vector<std::vector<int>> sets) {
288  int nsets = sets.size();
289  int minprod = std::numeric_limits<int>::max();
290  int maxprod = std::numeric_limits<int>::min();
291  for (int i = 0; i < nsets; i++) {
292  std::cerr << "Set " << i << ": ";
293  int prod = 1;
294  for (int const n : sets[i]) {
295  prod *= n;
296  std::cerr << n << " ";
297  }
298  std::cerr << "prod=" << prod << "\n";
299  if (prod < minprod)
300  minprod = prod;
301  if (prod > maxprod)
302  maxprod = prod;
303  }
304  int maxdiff = maxprod - minprod;
305  std::cerr << "Max diff: " << maxdiff << "\n";
306 }
307 #endif
308 
309 } // namespace Wonton
310 
311 #endif
Factorize a number N into D equal (or nearly equal) factors.
Definition: adaptive_refinement_mesh.h:31
std::vector< T > vector
Definition: wonton.h:285