- improved error logging
[oweals/gnunet.git] / src / ats / gnunet-service-ats_addresses_mlp.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file ats/gnunet-service-ats_addresses_mlp.c
23  * @brief ats mlp problem solver
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet-service-ats_addresses.h"
30 #include "gnunet-service-ats_addresses_mlp.h"
31 #include "gnunet_statistics_service.h"
32 #if HAVE_LIBGLPK
33 #include "glpk.h"
34 #endif
35
36 /**
37  * Translate glpk solver error codes to text
38  * @param retcode return code
39  * @return string with result
40  */
41 const char *
42 mlp_solve_to_string (int retcode)
43 {
44   switch (retcode) {
45     case 0:
46       return "ok";
47       break;
48     case GLP_EBADB:
49       return "invalid basis";
50       break;
51     case GLP_ESING:
52       return "singular matrix";
53       break;
54     case GLP_ECOND:
55       return "ill-conditioned matrix";
56       break;
57     case GLP_EBOUND:
58       return "invalid bounds";
59       break;
60     case GLP_EFAIL:
61       return "solver failed";
62       break;
63     case GLP_EOBJLL:
64       return "objective lower limit reached";
65       break;
66     case GLP_EOBJUL:
67       return "objective upper limit reached";
68       break;
69     case GLP_EITLIM:
70       return "iteration limit exceeded";
71       break;
72     case GLP_ETMLIM:
73       return "time limit exceeded";
74       break;
75     case GLP_ENOPFS:
76       return "no primal feasible solution";
77       break;
78     case GLP_EROOT:
79       return "root LP optimum not provided";
80       break;
81     case GLP_ESTOP:
82       return "search terminated by application";
83       break;
84     case GLP_EMIPGAP:
85       return "relative mip gap tolerance reached";
86       break;
87     case GLP_ENOFEAS:
88       return "no dual feasible solution";
89       break;
90     case GLP_ENOCVG:
91       return "no convergence";
92       break;
93     case GLP_EINSTAB:
94       return "numerical instability";
95       break;
96     case GLP_EDATA:
97       return "invalid data";
98       break;
99     case GLP_ERANGE:
100       return "result out of range";
101       break;
102     default:
103       GNUNET_break (0);
104       return "unknown error";
105       break;
106   }
107   GNUNET_break (0);
108   return "unknown error";
109 }
110
111
112 /**
113  * Translate glpk status error codes to text
114  * @param retcode return code
115  * @return string with result
116  */
117 const char *
118 mlp_status_to_string (int retcode)
119 {
120   switch (retcode) {
121     case GLP_UNDEF:
122       return "solution is undefined";
123       break;
124     case GLP_FEAS:
125       return "solution is feasible";
126       break;
127     case GLP_INFEAS:
128       return "solution is infeasible";
129       break;
130     case GLP_NOFEAS:
131       return "no feasible solution exists";
132       break;
133     case GLP_OPT:
134       return "solution is optimal";
135       break;
136     case GLP_UNBND:
137       return "solution is unbounded";
138       break;
139     default:
140       GNUNET_break (0);
141       return "unknown error";
142       break;
143   }
144   GNUNET_break (0);
145   return "unknown error";
146 }
147
148 /**
149  * Intercept GLPK terminal output
150  * @param info the mlp handle
151  * @param s the string to print
152  * @return 0: glpk prints output on terminal, 0 != surpress output
153  */
154 static int
155 mlp_term_hook (void *info, const char *s)
156 {
157   /* Not needed atm struct MLP_information *mlp = info; */
158   GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "%s", s);
159   return 1;
160 }
161
162 /**
163  * Create the MLP problem
164  *
165  * @param mlp the MLP handle
166  * @return GNUNET_OK or GNUNET_SYSERR
167  */
168
169 static int
170 mlp_create_problem (struct GAS_MLP_Handle *mlp)
171 {
172   int res = GNUNET_OK;
173   int col;
174   int c;
175   char *name;
176
177   /* Set a problem name */
178   glp_set_prob_name (mlp->prob, "gnunet ats bandwidth distribution");
179
180   /* Set optimization direction to maximize */
181   glp_set_obj_dir (mlp->prob, GLP_MAX);
182
183   /* Adding invariant columns */
184
185   /* Diversity d column  */
186
187   col = glp_add_cols (mlp->prob, 1);
188   mlp->c_d = col;
189   /* Column name */
190   glp_set_col_name (mlp->prob, col, "d");
191   /* Column objective function coefficient */
192   glp_set_obj_coef (mlp->prob, col, mlp->co_D);
193   /* Column lower bound = 0.0 */
194   glp_set_col_bnds (mlp->prob, col, GLP_LO, 0.0, 0.0);
195
196   /* Utilization u column  */
197
198   col = glp_add_cols (mlp->prob, 1);
199   mlp->c_u = col;
200   /* Column name */
201   glp_set_col_name (mlp->prob, col, "u");
202   /* Column objective function coefficient */
203   glp_set_obj_coef (mlp->prob, col, mlp->co_U);
204   /* Column lower bound = 0.0 */
205   glp_set_col_bnds (mlp->prob, col, GLP_LO, 0.0, 0.0);
206
207   /* Relativity r column  */
208   col = glp_add_cols (mlp->prob, 1);
209   mlp->c_r = col;
210   /* Column name */
211   glp_set_col_name (mlp->prob, col, "r");
212   /* Column objective function coefficient */
213   glp_set_obj_coef (mlp->prob, col, mlp->co_R);
214   /* Column lower bound = 0.0 */
215   glp_set_col_bnds (mlp->prob, col, GLP_LO, 0.0, 0.0);
216
217   /* Quality metric columns */
218   col = glp_add_cols(mlp->prob, mlp->m);
219   for (c = 0; c < mlp->m; c++)
220   {
221     mlp->c_q[c] = col + c;
222     GNUNET_asprintf (&name, "q_%u", mlp->q[c]);
223     glp_set_col_name (mlp->prob, col + c, name);
224     /* Column lower bound = 0.0 */
225     glp_set_col_bnds (mlp->prob, col + c, GLP_LO, 0.0, 0.0);
226     GNUNET_free (name);
227     /* Coefficient == Qm */
228     glp_set_obj_coef (mlp->prob, col + c, mlp->co_Q[c]);
229   }
230
231   return res;
232 }
233
234 /**
235  * Solves the LP problem
236  *
237  * @param mlp the MLP Handle
238  * @return GNUNET_OK if could be solved, GNUNET_SYSERR on failure
239  */
240 static int
241 mlp_solve_lp_problem (struct GAS_MLP_Handle *mlp)
242 {
243   int res;
244   struct GNUNET_TIME_Relative duration;
245   struct GNUNET_TIME_Absolute end;
246   struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get();
247
248   /* LP presolver?
249    * Presolver is required if the problem was modified and an existing
250    * valid basis is now invalid */
251   if (mlp->presolver_required == GNUNET_YES)
252     mlp->control_param_lp.presolve = GLP_ON;
253   else
254     mlp->control_param_lp.presolve = GLP_OFF;
255
256   /* Solve LP problem to have initial valid solution */
257 lp_solv:
258   res = glp_simplex(mlp->prob, &mlp->control_param_lp);
259   if (res == 0)
260   {
261     /* The LP problem instance has been successfully solved. */
262   }
263   else if (res == GLP_EITLIM)
264   {
265     /* simplex iteration limit has been exceeded. */
266     // TODO Increase iteration limit?
267   }
268   else if (res == GLP_ETMLIM)
269   {
270     /* Time limit has been exceeded.  */
271     // TODO Increase time limit?
272   }
273   else
274   {
275     /* Problem was ill-defined, retry with presolver */
276     if (mlp->presolver_required == GNUNET_NO)
277     {
278       mlp->presolver_required = GNUNET_YES;
279       goto lp_solv;
280     }
281     else
282     {
283       /* Problem was ill-defined, no way to handle that */
284       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
285           "ats-mlp",
286           "Solving LP problem failed:  %s\n", mlp_solve_to_string(res));
287       return GNUNET_SYSERR;
288     }
289   }
290
291   end = GNUNET_TIME_absolute_get ();
292   duration = GNUNET_TIME_absolute_get_difference (start, end);
293   mlp->lp_solved++;
294   mlp->lp_total_duration =+ duration.rel_value;
295
296   GNUNET_STATISTICS_update (mlp->stats,"# LP problem solved", 1, GNUNET_NO);
297   GNUNET_STATISTICS_set (mlp->stats,"# LP execution time", duration.rel_value, GNUNET_NO);
298   GNUNET_STATISTICS_set (mlp->stats,"# LP execution time average",
299                          mlp->lp_total_duration / mlp->lp_solved,  GNUNET_NO);
300
301
302   /* Analyze problem status  */
303   res = glp_get_status (mlp->prob);
304   switch (res) {
305     /* solution is optimal */
306     case GLP_OPT:
307     /* solution is feasible */
308     case GLP_FEAS:
309       break;
310
311     /* Problem was ill-defined, no way to handle that */
312     default:
313       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
314           "ats-mlp",
315           "Solving LP problem failed, no solution: %s\n", mlp_status_to_string(res));
316       return GNUNET_SYSERR;
317       break;
318   }
319
320   /* solved sucessfully, no presolver required next time */
321   mlp->presolver_required = GNUNET_NO;
322
323   return GNUNET_OK;
324 }
325
326
327 /**
328  * Solves the MLP problem
329  *
330  * @param mlp the MLP Handle
331  * @return GNUNET_OK if could be solved, GNUNET_SYSERR on failure
332  */
333 int
334 mlp_solve_mlp_problem (struct GAS_MLP_Handle *mlp)
335 {
336   int res;
337   struct GNUNET_TIME_Relative duration;
338   struct GNUNET_TIME_Absolute end;
339   struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get();
340
341   /* solve MLP problem */
342   res = glp_intopt(mlp->prob, &mlp->control_param_mlp);
343
344   if (res == 0)
345   {
346     /* The MLP problem instance has been successfully solved. */
347   }
348   else if (res == GLP_EITLIM)
349   {
350     /* simplex iteration limit has been exceeded. */
351     // TODO Increase iteration limit?
352   }
353   else if (res == GLP_ETMLIM)
354   {
355     /* Time limit has been exceeded.  */
356     // TODO Increase time limit?
357   }
358   else
359   {
360     /* Problem was ill-defined, no way to handle that */
361     GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
362         "ats-mlp",
363         "Solving MLP problem failed:  %s\n", mlp_solve_to_string(res));
364     return GNUNET_SYSERR;
365   }
366
367   end = GNUNET_TIME_absolute_get ();
368   duration = GNUNET_TIME_absolute_get_difference (start, end);
369   mlp->mlp_solved++;
370   mlp->mlp_total_duration =+ duration.rel_value;
371
372   GNUNET_STATISTICS_update (mlp->stats,"# MLP problem solved", 1, GNUNET_NO);
373   GNUNET_STATISTICS_set (mlp->stats,"# MLP execution time", duration.rel_value, GNUNET_NO);
374   GNUNET_STATISTICS_set (mlp->stats,"# MLP execution time average",
375                          mlp->mlp_total_duration / mlp->mlp_solved,  GNUNET_NO);
376
377   /* Analyze problem status  */
378   res = glp_mip_status(mlp->prob);
379   switch (res) {
380     /* solution is optimal */
381     case GLP_OPT:
382     /* solution is feasible */
383     case GLP_FEAS:
384       break;
385
386     /* Problem was ill-defined, no way to handle that */
387     default:
388       GNUNET_log_from (GNUNET_ERROR_TYPE_ERROR,
389           "ats-mlp",
390           "Solving MLP problem failed, %s\n\n", mlp_status_to_string(res));
391       return GNUNET_SYSERR;
392       break;
393   }
394
395   return GNUNET_OK;
396 }
397
398 /**
399  * Solves the MLP problem
400  *
401  * @param mlp the MLP Handle
402  * @return GNUNET_OK if could be solved, GNUNET_SYSERR on failure
403  */
404 int
405 mlp_solve_problem (struct GAS_MLP_Handle *mlp)
406 {
407   int res;
408   mlp->last_execution = GNUNET_TIME_absolute_get ();
409   res = mlp_solve_lp_problem (mlp);
410   if (res == GNUNET_OK)
411     res = mlp_solve_mlp_problem (mlp);
412   return res;
413 }
414
415 /**
416  * Init the MLP problem solving component
417  *
418  * @param stats the GNUNET_STATISTICS handle
419  * @param max_duration maximum numbers of iterations for the LP/MLP Solver
420  * @param max_iterations maximum time limit for the LP/MLP Solver
421  * @return struct GAS_MLP_Handle * on success, NULL on fail
422  */
423 struct GAS_MLP_Handle *
424 GAS_mlp_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
425               const struct GNUNET_STATISTICS_Handle *stats,
426               struct GNUNET_TIME_Relative max_duration,
427               unsigned int max_iterations)
428 {
429   struct GAS_MLP_Handle * mlp = GNUNET_malloc (sizeof (struct GAS_MLP_Handle));
430
431   double D;
432   double R;
433   double U;
434   long long unsigned int tmp;
435   unsigned int b_min;
436   unsigned int n_min;
437
438   /* Init GLPK environment */
439   GNUNET_assert (glp_init_env() == 0);
440
441   /* Create initial MLP problem */
442   mlp->prob = glp_create_prob();
443   GNUNET_assert (mlp->prob != NULL);
444
445   /* Get diversity coefficient from configuration */
446   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
447                                                       "COEFFICIENT_D",
448                                                       &tmp))
449     D = (double) tmp / 100;
450   else
451     D = 1.0;
452
453   /* Get proportionality coefficient from configuration */
454   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
455                                                       "COEFFICIENT_R",
456                                                       &tmp))
457     R = (double) tmp / 100;
458   else
459     R = 1.0;
460
461   /* Get utilization coefficient from configuration */
462   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
463                                                       "COEFFICIENT_U",
464                                                       &tmp))
465     U = (double) tmp / 100;
466   else
467     U = 1.0;
468
469   /* Get quality metric coefficients from configuration */
470   int i_delay = -1;
471   int i_distance = -1;
472   int q[GNUNET_ATS_QualityPropertiesCount] = GNUNET_ATS_QualityProperties;
473   int c;
474   for (c = 0; c < GNUNET_ATS_QualityPropertiesCount; c++)
475   {
476     /* initialize quality coefficients with default value 1.0 */
477     mlp->co_Q[c] = 1.0;
478
479     mlp->q[c] = q[c];
480     if (q[c] == GNUNET_ATS_QUALITY_NET_DELAY)
481       i_delay = c;
482     if (q[c] == GNUNET_ATS_QUALITY_NET_DISTANCE)
483       i_distance = c;
484   }
485
486   if ((i_delay != -1) && (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
487                                                       "COEFFICIENT_QUALITY_DELAY",
488                                                       &tmp)))
489
490     mlp->co_Q[i_delay] = (double) tmp / 100;
491   else
492     mlp->co_Q[i_delay] = 1.0;
493
494   if ((i_distance != -1) && (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
495                                                       "COEFFICIENT_QUALITY_DISTANCE",
496                                                       &tmp)))
497     mlp->co_Q[i_distance] = (double) tmp / 100;
498   else
499     mlp->co_Q[i_distance] = 1.0;
500
501   /* Get minimum bandwidth per used address from configuration */
502   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
503                                                       "MIN_BANDWIDTH",
504                                                       &tmp))
505     b_min = tmp;
506   else
507     b_min = 64000;
508
509   /* Get minimum number of connections from configuration */
510   if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
511                                                       "MIN_CONNECTIONS",
512                                                       &tmp))
513     n_min = tmp;
514   else
515     n_min = 4;
516
517   mlp->stats = (struct GNUNET_STATISTICS_Handle *) stats;
518   mlp->max_iterations = max_iterations;
519   mlp->max_exec_duration = max_duration;
520
521   /* Redirect GLPK output to GNUnet logging */
522   glp_error_hook((void *) mlp, &mlp_term_hook);
523
524   /* Init LP solving parameters */
525   glp_init_smcp(&mlp->control_param_lp);
526 #if DEBUG_MLP
527   mlp->control_param_lp.msg_lev = GLP_MSG_ALL;
528 #else
529   mlp->control_param_lp.msg_lev = GLP_MSG_OFF;
530 #endif
531   mlp->control_param_lp.it_lim = max_iterations;
532   mlp->control_param_lp.tm_lim = max_duration.rel_value;
533
534   /* Init MLP solving parameters */
535   glp_init_iocp(&mlp->control_param_mlp);
536 #if DEBUG_MLP
537   mlp->control_param_mlp.msg_lev = GLP_MSG_ALL;
538 #else
539   mlp->control_param_mlp.msg_lev = GLP_MSG_OFF;
540 #endif
541   mlp->control_param_mlp.tm_lim = max_duration.rel_value;
542
543   mlp->last_execution = GNUNET_TIME_absolute_get_forever();
544
545   mlp->co_D = D;
546   mlp->co_R = R;
547   mlp->co_U = U;
548   mlp->b_min = b_min;
549   mlp->n_min = n_min;
550   mlp->m = GNUNET_ATS_QualityPropertiesCount;
551
552   mlp_create_problem (mlp);
553   return mlp;
554 }
555
556 /**
557  * Updates a single address in the MLP problem
558  *
559  * If the address did not exist before in the problem:
560  * The MLP problem has to be recreated and the problem has to be resolved
561  *
562  * Otherwise the addresses' values can be updated and the existing base can
563  * be reused
564  *
565  * @param mlp the MLP Handle
566  * @param addresses the address hashmap
567  * @param address the address to update
568  */
569 void
570 GAS_mlp_address_update (struct GAS_MLP_Handle *mlp, struct GNUNET_CONTAINER_MultiHashMap * addresses, struct ATS_Address *address)
571 {
572   int new;
573   int col;
574   struct MLP_information *mlpi;
575   char * name;
576
577   GNUNET_STATISTICS_update (mlp->stats,"# LP address updates", 1, GNUNET_NO);
578
579   /* We add a new address */
580   if (address->mlp_information == NULL)
581   {
582     new = GNUNET_YES;
583     mlpi = GNUNET_malloc (sizeof (struct MLP_information));
584     address->mlp_information = mlpi;
585
586     /* Add bandwidth column */
587     col = glp_add_cols (mlp->prob, 2);
588     mlpi->c_b = col;
589     mlpi->c_n = col + 1;
590
591     GNUNET_asprintf (&name, "b_%s_%s", GNUNET_i2s (&address->peer), address->plugin);
592     glp_set_col_name (mlp->prob, mlpi->c_b , name);
593     GNUNET_free (name);
594     /* Lower bound == 0 */
595     glp_set_col_bnds (mlp->prob, mlpi->c_b , GLP_LO, 0.0, 0.0);
596     /* Continuous value*/
597     glp_set_col_kind (mlp->prob, mlpi->c_b , GLP_CV);
598     /* Objective function coefficient == 0 */
599     glp_set_obj_coef (mlp->prob, mlpi->c_b , 0);
600
601     /* Add usage column */
602     GNUNET_asprintf (&name, "n_%s_%s", GNUNET_i2s (&address->peer), address->plugin);
603     glp_set_col_name (mlp->prob, mlpi->c_n, name);
604     GNUNET_free (name);
605     /* Limit value : 0 <= value <= 1 */
606     glp_set_col_bnds (mlp->prob, mlpi->c_n, GLP_DB, 0.0, 1.0);
607     /* Integer value*/
608     glp_set_col_kind (mlp->prob, mlpi->c_n, GLP_IV);
609     /* Objective function coefficient == 0 */
610     glp_set_obj_coef (mlp->prob, mlpi->c_n, 0);
611
612     /* Add */
613   }
614   else
615     new = GNUNET_NO;
616
617   /* Do the update */
618
619   /* Recalculate */
620   if (new == GNUNET_YES)
621     mlp->presolver_required = GNUNET_YES;
622   mlp_solve_problem (mlp);
623 }
624
625 /**
626  * Deletes a single address in the MLP problem
627  *
628  * The MLP problem has to be recreated and the problem has to be resolved
629  *
630  * @param mlp the MLP Handle
631  * @param addresses the address hashmap
632  * @param address the address to delete
633  */
634 void
635 GAS_mlp_address_delete (struct GAS_MLP_Handle *mlp, struct GNUNET_CONTAINER_MultiHashMap * addresses, struct ATS_Address *address)
636 {
637   GNUNET_STATISTICS_update (mlp->stats,"# LP address deletions", 1, GNUNET_NO);
638
639   /* Free resources */
640   if (address->mlp_information != NULL)
641   {
642     GNUNET_free (address->mlp_information);
643     address->mlp_information = NULL;
644   }
645
646   /* Update problem */
647
648   /* Recalculate */
649   mlp->presolver_required = GNUNET_YES;
650   mlp_solve_problem (mlp);
651 }
652
653 /**
654  * Deletes a single address in the MLP problem
655  *
656  * @param mlp the MLP Handle
657  * @param addresses the address hashmap
658  * @param address the address to change the preference
659  */
660 void
661 GAS_mlp_address_change_preference (struct GAS_MLP_Handle *mlp, struct GNUNET_CONTAINER_MultiHashMap * addresses, struct ATS_Address *address)
662 {
663   GNUNET_STATISTICS_update (mlp->stats,"# LP address preference changes", 1, GNUNET_NO);
664 }
665
666 /**
667  * Shutdown the MLP problem solving component
668  * @param mlp the MLP handle
669  */
670 void
671 GAS_mlp_done (struct GAS_MLP_Handle *mlp)
672 {
673   if (mlp != NULL)
674     glp_delete_prob(mlp->prob);
675
676   /* Clean up GLPK environment */
677   glp_free_env();
678
679   GNUNET_free (mlp);
680 }
681
682
683 /* end of gnunet-service-ats_addresses_mlp.c */