Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
ptask_BMF: refactor code and loop scenarios
[simgrid.git] / src / kernel / lmm / bmf.cpp
1 /* Copyright (c) 2007-2022. The SimGrid Team. All rights reserved.          */
2
3 /* This program is free software; you can redistribute it and/or modify it
4  * under the terms of the license (GNU LGPL) which comes with this package. */
5
6 #include "src/kernel/lmm/bmf.hpp"
7 #include <eigen3/Eigen/LU>
8 #include <iostream>
9 #include <numeric>
10 #include <sstream>
11
12 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(ker_bmf, kernel, "Kernel BMF solver");
13
14 int sg_bmf_max_iterations = 1000; /* Change this with --cfg=bmf/max-iterations:VALUE */
15
16 namespace simgrid {
17 namespace kernel {
18 namespace lmm {
19
20 AllocationGenerator::AllocationGenerator(Eigen::MatrixXd A) : A_(std::move(A)), alloc_(A_.cols(), 0)
21 {
22   // got a first valid allocation
23   for (size_t p = 0; p < alloc_.size(); p++) {
24     for (int r = 0; r < A_.rows(); r++) {
25       if (A_(r, p) > 0) {
26         alloc_[p] = r;
27         break;
28       }
29     }
30   }
31 }
32
33 bool AllocationGenerator::next(std::vector<int>& next_alloc)
34 {
35   if (first_) {
36     next_alloc = alloc_;
37     first_     = false;
38     return true;
39   }
40
41   int n_resources = A_.rows();
42   size_t idx      = 0;
43   while (idx < alloc_.size()) {
44     alloc_[idx] = (++alloc_[idx]) % n_resources;
45     if (alloc_[idx] == 0) {
46       idx++;
47       continue;
48     } else {
49       idx = 0;
50     }
51     if (A_(alloc_[idx], idx) > 0) {
52       next_alloc = alloc_;
53       return true;
54     }
55   }
56   return false;
57 }
58
59 /*****************************************************************************/
60
61 BmfSolver::BmfSolver(Eigen::MatrixXd A, Eigen::MatrixXd maxA, Eigen::VectorXd C, Eigen::VectorXd phi)
62     : A_(std::move(A)), maxA_(std::move(maxA)), C_(std::move(C)), phi_(std::move(phi)), gen_(A_)
63 {
64   xbt_assert(max_iteration_ > 0,
65              "Invalid number of iterations for BMF solver. Please check your \"bmf/max-iterations\" configuration.");
66 }
67
68 template <typename T> std::string BmfSolver::debug_eigen(const T& obj) const
69 {
70   std::stringstream debug;
71   debug << obj;
72   return debug.str();
73 }
74
75 template <typename C> std::string BmfSolver::debug_vector(const C& container) const
76 {
77   std::stringstream debug;
78   std::copy(container.begin(), container.end(),
79             std::ostream_iterator<typename std::remove_reference<decltype(container)>::type::value_type>(debug, " "));
80   return debug.str();
81 }
82
83 std::string BmfSolver::debug_alloc(const allocation_map_t& alloc) const
84 {
85   std::stringstream debug;
86   for (const auto& e : alloc) {
87     debug << "{" + std::to_string(e.first) + ": [" + debug_vector(e.second) + "]}, ";
88   }
89   return debug.str();
90 }
91
92 double BmfSolver::get_resource_capacity(int resource, const std::vector<int>& bounded_players) const
93 {
94   double capacity = C_[resource];
95   for (int p : bounded_players) {
96     capacity -= A_(resource, p) * phi_[p];
97   }
98   return capacity;
99 }
100
101 std::vector<int> BmfSolver::alloc_map_to_vector(const allocation_map_t& alloc) const
102 {
103   std::vector<int> alloc_by_player(A_.cols(), -1);
104   for (auto it : alloc) {
105     for (auto p : it.second) {
106       alloc_by_player[p] = it.first;
107     }
108   }
109   return alloc_by_player;
110 }
111
112 Eigen::VectorXd BmfSolver::equilibrium(const allocation_map_t& alloc) const
113 {
114   int n_players       = A_.cols();
115   Eigen::MatrixXd A_p = Eigen::MatrixXd::Zero(n_players, n_players); // square matrix with number of players
116   Eigen::VectorXd C_p = Eigen::VectorXd::Zero(n_players);
117
118   // iterate over alloc to verify if 2 players have chosen the same resource
119   // if so, they must have a fair sharing of this resource, adjust A_p and C_p accordingly
120   int last_row  = n_players - 1;
121   int first_row = 0;
122   std::vector<int> bounded_players;
123   for (const auto& e : alloc) {
124     // add one row for the resource with A[r,]
125     int cur_resource = e.first;
126     if (cur_resource == NO_RESOURCE) {
127       bounded_players.insert(bounded_players.end(), e.second.begin(), e.second.end());
128       continue;
129     }
130     A_p.row(first_row) = A_.row(cur_resource);
131     C_p[first_row]     = get_resource_capacity(cur_resource, bounded_players);
132     first_row++;
133     if (e.second.size() > 1) {
134       auto it = e.second.begin();
135       int i   = *it; // first player
136       /* for each other player sharing this resource */
137       for (++it; it != e.second.end(); ++it) {
138         /* player i and k on this resource j: so maxA_ji*rho_i - maxA_jk*rho_k = 0 */
139         int k            = *it;
140         C_p[last_row]    = 0;
141         A_p(last_row, i) = maxA_(cur_resource, i);
142         A_p(last_row, k) = -maxA_(cur_resource, k);
143         last_row--;
144       }
145     }
146   }
147   /* clear players which are externally bounded */
148   for (int p : bounded_players) {
149     A_p.col(p).setZero();
150   }
151
152   XBT_DEBUG("A':\n%s", debug_eigen(A_p).c_str());
153
154   XBT_DEBUG("C':\n%s", debug_eigen(C_p).c_str());
155   Eigen::VectorXd rho = Eigen::FullPivLU<Eigen::MatrixXd>(A_p).solve(C_p);
156   for (int p : bounded_players) {
157     rho[p] = phi_[p];
158   }
159   return rho;
160 }
161
162 bool BmfSolver::disturb_allocation(allocation_map_t& alloc, std::vector<int>& alloc_by_player)
163 {
164   while (gen_.next(alloc_by_player)) {
165     if (allocations_.find(alloc_by_player) == allocations_.end()) {
166       allocations_.clear();
167       allocations_.insert(alloc_by_player);
168       alloc.clear();
169       for (size_t p = 0; p < alloc_by_player.size(); p++) {
170         alloc[alloc_by_player[p]].insert(p);
171       }
172       return false;
173     }
174   }
175   return true;
176 }
177
178 bool BmfSolver::get_alloc(const Eigen::VectorXd& fair_sharing, const allocation_map_t& last_alloc,
179                           allocation_map_t& alloc, bool initial)
180 {
181   alloc.clear();
182   for (int player_idx = 0; player_idx < A_.cols(); player_idx++) {
183     int selected_resource = NO_RESOURCE;
184     double bound          = phi_[player_idx];
185     double min_share      = (bound <= 0 || initial) ? -1 : bound;
186     for (int cnst_idx = 0; cnst_idx < A_.rows(); cnst_idx++) {
187       if (A_(cnst_idx, player_idx) <= 0.0)
188         continue;
189
190       double share = fair_sharing[cnst_idx] / A_(cnst_idx, player_idx);
191       if (min_share == -1 || double_positive(min_share - share, sg_maxmin_precision)) {
192         selected_resource = cnst_idx;
193         min_share         = share;
194       }
195     }
196     alloc[selected_resource].insert(player_idx);
197   }
198   bool is_stable = (alloc == last_alloc);
199   if (is_stable)
200     return true;
201
202   std::vector<int> alloc_by_player      = alloc_map_to_vector(alloc);
203 #if 0
204   std::vector<int> last_alloc_by_player = alloc_map_to_vector(last_alloc);
205   if (not initial) {
206     std::for_each(allocations_age_.begin(), allocations_age_.end(), [](int& n) { n++; });
207     std::vector<int> age_idx(allocations_age_.size());
208     std::iota(age_idx.begin(), age_idx.end(), 0);
209     std::stable_sort(age_idx.begin(), age_idx.end(),
210                      [this](auto a, auto b) { return this->allocations_age_[a] > this->allocations_age_[b]; });
211     for (int p : age_idx) {
212       if (alloc_by_player[p] != last_alloc_by_player[p]) {
213         alloc = last_alloc;
214         alloc[last_alloc_by_player[p]].erase(p);
215         if (alloc[last_alloc_by_player[p]].empty())
216           alloc.erase(last_alloc_by_player[p]);
217         alloc[alloc_by_player[p]].insert(p);
218         allocations_age_[p] = 0;
219       }
220     }
221     alloc_by_player = alloc_map_to_vector(alloc);
222   }
223 #endif
224   auto ret = allocations_.insert(alloc_by_player);
225   /* oops, allocation already tried, let's pertube it a bit */
226   if (not ret.second) {
227     XBT_DEBUG("Allocation already tried: %s", debug_alloc(alloc).c_str());
228     return disturb_allocation(alloc, alloc_by_player);
229   }
230   return false;
231 }
232
233 void BmfSolver::set_fair_sharing(const allocation_map_t& alloc, const Eigen::VectorXd& rho,
234                                  Eigen::VectorXd& fair_sharing) const
235 {
236   for (int r = 0; r < fair_sharing.size(); r++) {
237     auto it = alloc.find(r);
238     if (it != alloc.end()) {              // resource selected by some player, fair share depends on rho
239       int player = *(it->second.begin()); // equilibrium assures that every player receives the same, use one of them to
240                                           // calculate the fair sharing for resource r
241       fair_sharing[r] = A_(r, player) * rho[player];
242     } else { // nobody selects this resource, fair_sharing depends on resource saturation
243       // resource r is saturated (A[r,*] * rho > C), divide it among players
244       double consumption_r = A_.row(r) * rho;
245       double_update(&consumption_r, C_[r], sg_maxmin_precision);
246       if (consumption_r > 0.0) {
247         int n_players   = std::count_if(A_.row(r).data(), A_.row(r).data() + A_.row(r).size(),
248                                       [](double v) { return double_positive(v, sg_maxmin_precision); });
249         fair_sharing[r] = C_[r] / n_players;
250       } else {
251         fair_sharing[r] = C_[r];
252       }
253     }
254   }
255 }
256
257 bool BmfSolver::is_bmf(const Eigen::VectorXd& rho) const
258 {
259   bool bmf = true;
260
261   // 1) the capacity of all resources is respected
262   Eigen::VectorXd remaining = (A_ * rho) - C_;
263   bmf                       = bmf && (not std::any_of(remaining.data(), remaining.data() + remaining.size(),
264                                 [](double v) { return double_positive(v, sg_maxmin_precision); }));
265
266   // 3) every player receives maximum share in at least 1 saturated resource
267   // due to subflows, compare with the maximum consumption and not the A matrix
268   Eigen::MatrixXd usage =
269       maxA_.array().rowwise() * rho.transpose().array(); // usage_ji: indicates the usage of player i on resource j
270
271   XBT_DEBUG("Usage_ji considering max consumption:\n%s", debug_eigen(usage).c_str());
272   auto max_share = usage.rowwise().maxCoeff(); // max share for each resource j
273
274   // matrix_ji: boolean indicating player p has the maximum share at resource j
275   Eigen::MatrixXi player_max_share =
276       ((usage.array().colwise() - max_share.array()).abs() <= sg_maxmin_precision).cast<int>();
277   // but only saturated resources must be considered
278   Eigen::VectorXi saturated = ((remaining.array().abs() <= sg_maxmin_precision)).cast<int>();
279   XBT_DEBUG("Saturated_j resources:\n%s", debug_eigen(saturated).c_str());
280   player_max_share.array().colwise() *= saturated.array();
281
282   // just check if it has received at least it's bound
283   for (int p = 0; p < rho.size(); p++) {
284     if (double_equals(rho[p], phi_[p], sg_maxmin_precision)) {
285       player_max_share(0, p) = 1; // it doesn't really matter, just to say that it's a bmf
286       saturated[0]           = 1;
287     }
288   }
289
290   // 2) at least 1 resource is saturated
291   bmf = bmf && (saturated.array() == 1).any();
292
293   XBT_DEBUG("Player_ji usage of saturated resources:\n%s", debug_eigen(player_max_share).c_str());
294   // for all columns(players) it has to be the max at least in 1
295   bmf = bmf && (player_max_share.colwise().sum().all() >= 1);
296   return bmf;
297 }
298
299 Eigen::VectorXd BmfSolver::solve()
300 {
301   XBT_DEBUG("Starting BMF solver");
302
303   XBT_DEBUG("A:\n%s", debug_eigen(A_).c_str());
304   XBT_DEBUG("maxA:\n%s", debug_eigen(maxA_).c_str());
305   XBT_DEBUG("C:\n%s", debug_eigen(C_).c_str());
306
307   /* no flows to share, just returns */
308   if (A_.cols() == 0)
309     return {};
310
311   int it            = 0;
312   auto fair_sharing = C_;
313
314   /* BMF allocation for each player (current and last one) stop when are equal */
315   allocation_map_t last_alloc, cur_alloc;
316   Eigen::VectorXd rho;
317
318   while (it < max_iteration_ && not get_alloc(fair_sharing, last_alloc, cur_alloc, it == 0)) {
319     last_alloc = cur_alloc;
320     XBT_DEBUG("BMF: iteration %d", it);
321     XBT_DEBUG("B (current allocation): %s", debug_alloc(cur_alloc).c_str());
322
323     // solve inv(A)*rho = C
324     rho = equilibrium(cur_alloc);
325     XBT_DEBUG("rho:\n%s", debug_eigen(rho).c_str());
326
327     // get fair sharing for each resource
328     set_fair_sharing(cur_alloc, rho, fair_sharing);
329     XBT_DEBUG("Fair sharing vector (per resource):\n%s", debug_eigen(fair_sharing).c_str());
330
331     // get new allocation for players
332     it++;
333   }
334
335   /* Not mandatory but a safe check to assure we have a proper solution */
336   if (not is_bmf(rho)) {
337     fprintf(stderr, "Unable to find a BMF allocation for your system.\n"
338                     "You may try to increase the maximum number of iterations performed by BMF solver "
339                     "(\"--cfg=bmf/max-iterations\").\n"
340                     "Additionally, you could decrease numerical precision (\"--cfg=surf/precision\").\n");
341     fprintf(stderr, "Internal states (after %d iterations):\n", it);
342     fprintf(stderr, "A:\n%s\n", debug_eigen(A_).c_str());
343     fprintf(stderr, "maxA:\n%s\n", debug_eigen(maxA_).c_str());
344     fprintf(stderr, "C:\n%s\n", debug_eigen(C_).c_str());
345     fprintf(stderr, "rho:\n%s\n", debug_eigen(rho).c_str());
346     xbt_abort();
347   }
348
349   XBT_DEBUG("BMF done after %d iterations", it);
350   return rho;
351 }
352
353 /*****************************************************************************/
354
355 void BmfSystem::get_flows_data(Eigen::MatrixXd& A, Eigen::MatrixXd& maxA, Eigen::VectorXd& phi)
356 {
357   A.resize(active_constraint_set.size(), variable_set.size());
358   A.setZero();
359   maxA.resize(active_constraint_set.size(), variable_set.size());
360   maxA.setZero();
361   phi.resize(variable_set.size());
362
363   int var_idx = 0;
364   for (Variable& var : variable_set) {
365     if (var.sharing_penalty_ <= 0)
366       continue;
367     bool active = false;
368     var.value_  = 1; // assign something by default for tasks with 0 consumption
369     for (const Element& elem : var.cnsts_) {
370       double consumption = elem.consumption_weight;
371       if (consumption > 0) {
372         int cnst_idx            = cnst2idx_[elem.constraint];
373         A(cnst_idx, var_idx)    = consumption;
374         maxA(cnst_idx, var_idx) = elem.max_consumption_weight;
375         active                  = true;
376       }
377     }
378     if (active) {
379       phi[var_idx] = var.get_bound();
380       idx2Var_[var_idx] = &var;
381       var_idx++;
382     }
383   }
384   // resize matrix to active variables only
385   A.conservativeResize(Eigen::NoChange_t::NoChange, var_idx);
386   maxA.conservativeResize(Eigen::NoChange_t::NoChange, var_idx);
387   phi.conservativeResize(var_idx);
388 }
389
390 void BmfSystem::get_constraint_data(Eigen::VectorXd& C)
391 {
392   C.resize(active_constraint_set.size());
393   cnst2idx_.clear();
394   int cnst_idx = 0;
395   for (const Constraint& cnst : active_constraint_set) {
396     C(cnst_idx)      = cnst.bound_;
397     cnst2idx_[&cnst] = cnst_idx;
398     cnst_idx++;
399   }
400 }
401
402 void BmfSystem::bmf_solve()
403 {
404   if (not modified_)
405     return;
406
407   /* initialize players' weight and constraint matrices */
408   idx2Var_.clear();
409   cnst2idx_.clear();
410   Eigen::MatrixXd A, maxA;
411   Eigen::VectorXd C, bounds;
412   get_constraint_data(C);
413   get_flows_data(A, maxA, bounds);
414
415   auto solver = BmfSolver(A, maxA, C, bounds);
416   auto rho    = solver.solve();
417
418   if (rho.size() == 0)
419     return;
420
421   /* setting rhos */
422   for (int i = 0; i < rho.size(); i++) {
423     idx2Var_[i]->value_ = rho[i];
424   }
425
426   print();
427 }
428
429 } // namespace lmm
430 } // namespace kernel
431 } // namespace simgrid