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