@@ -341,68 +341,329 @@ s64 node_balance(const struct graph *graph,
341341 return balance ;
342342}
343343
344+ /* Helper.
345+ * Compute the reduced cost of an arc. */
346+ static inline s64 reduced_cost (const struct graph * graph , const struct arc arc ,
347+ const s64 * cost , const s64 * potential )
348+ {
349+ struct node src = arc_tail (graph , arc );
350+ struct node dst = arc_head (graph , arc );
351+ return cost [arc .idx ] - potential [src .idx ] + potential [dst .idx ];
352+ }
353+
354+ /* Finds an optimal path from the source to the nearest sink node, by definition
355+ * a node i is a sink if node_balance[i]<0. It uses a reduced cost:
356+ * reduced_cost[i,j] = cost[i,j] - potential[i] + potential[j]
357+ *
358+ * */
359+ static struct node dijkstra_nearest_sink (const tal_t * ctx ,
360+ const struct graph * graph ,
361+ const struct node source ,
362+ const s64 * node_balance ,
363+ const s64 * capacity ,
364+ const s64 cap_threshold ,
365+ const s64 * cost ,
366+ const s64 * potential ,
367+ struct arc * prev ,
368+ s64 * distance )
369+ {
370+ struct node target = {.idx = INVALID_INDEX };
371+ tal_t * this_ctx = tal (ctx , tal_t );
372+
373+ if (!this_ctx )
374+ /* bad allocation */
375+ goto finish ;
376+
377+ /* check preconditions */
378+ assert (graph );
379+ assert (node_balance );
380+ assert (capacity );
381+ assert (cost );
382+ assert (potential );
383+ assert (prev );
384+ assert (distance );
385+
386+ const size_t max_num_arcs = graph_max_num_arcs (graph );
387+ const size_t max_num_nodes = graph_max_num_nodes (graph );
388+
389+ assert (source .idx < max_num_nodes );
390+ assert (tal_count (node_balance ) == max_num_nodes );
391+ assert (tal_count (capacity ) == max_num_arcs );
392+ assert (tal_count (cost ) == max_num_arcs );
393+ assert (tal_count (potential ) == max_num_nodes );
394+ assert (tal_count (prev ) == max_num_nodes );
395+ assert (tal_count (distance ) == max_num_nodes );
396+
397+ for (size_t i = 0 ; i < max_num_arcs ; i ++ ) {
398+ /* is this arc saturated? */
399+ if (capacity [i ] < cap_threshold )
400+ continue ;
401+
402+ struct arc arc = {.idx = i };
403+ struct node tail = arc_tail (graph , arc );
404+ struct node head = arc_head (graph , arc );
405+ s64 red_cost =
406+ cost [i ] - potential [tail .idx ] + potential [head .idx ];
407+
408+ /* reducted cost cannot be negative for non saturated arcs,
409+ * otherwise Dijkstra does not work. */
410+ if (red_cost < 0 )
411+ goto finish ;
412+ }
413+
414+ for (size_t i = 0 ; i < max_num_nodes ; ++ i )
415+ prev [i ].idx = INVALID_INDEX ;
416+
417+ /* Only in debug mode we keep track of visited nodes. */
418+ #ifndef NDEBUG
419+ bitmap * visited =
420+ tal_arrz (this_ctx , bitmap , BITMAP_NWORDS (max_num_nodes ));
421+ assert (visited );
422+ #endif
423+
424+ struct priorityqueue * q ;
425+ q = priorityqueue_new (this_ctx , max_num_nodes );
426+ const s64 * const dijkstra_distance = priorityqueue_value (q );
427+
428+ priorityqueue_init (q );
429+ priorityqueue_update (q , source .idx , 0 );
430+
431+ while (!priorityqueue_empty (q )) {
432+ const u32 idx = priorityqueue_top (q );
433+ const struct node cur = {.idx = idx };
434+ priorityqueue_pop (q );
435+
436+ /* Only in debug mode we keep track of visited nodes. */
437+ #ifndef NDEBUG
438+ assert (!bitmap_test_bit (visited , cur .idx ));
439+ bitmap_set_bit (visited , cur .idx );
440+ #endif
441+
442+ if (node_balance [cur .idx ] < 0 ) {
443+ target = cur ;
444+ break ;
445+ }
446+
447+ for (struct arc arc = node_adjacency_begin (graph , cur );
448+ !node_adjacency_end (arc );
449+ arc = node_adjacency_next (graph , arc )) {
450+ /* check if this arc is traversable */
451+ if (capacity [arc .idx ] < cap_threshold )
452+ continue ;
453+
454+ const struct node next = arc_head (graph , arc );
455+
456+ const s64 cij = cost [arc .idx ] - potential [cur .idx ] +
457+ potential [next .idx ];
458+
459+ /* Dijkstra only works with non-negative weights */
460+ assert (cij >= 0 );
461+
462+ if (dijkstra_distance [next .idx ] <=
463+ dijkstra_distance [cur .idx ] + cij )
464+ continue ;
465+
466+ priorityqueue_update (q , next .idx ,
467+ dijkstra_distance [cur .idx ] + cij );
468+ prev [next .idx ] = arc ;
469+ }
470+ }
471+ for (size_t i = 0 ; i < max_num_nodes ; i ++ )
472+ distance [i ] = dijkstra_distance [i ];
473+
474+ finish :
475+ tal_free (this_ctx );
476+ return target ;
477+ }
478+
479+ /* Problem: find a potential and capacity redistribution such that:
480+ * excess[all nodes] = 0
481+ * capacity[all arcs] >= 0
482+ * cost/potential [i,j] < 0 implies capacity[i,j] = 0
483+ *
484+ * Q. Is this a feasible solution?
485+ *
486+ * A. If we use flow conserving function sendflow, then
487+ * if for all nodes excess[i] = 0 and capacity[i,j] >= 0 for all arcs
488+ * then we have reached a feasible flow.
489+ *
490+ * Q. Is this flow optimal?
491+ *
492+ * A. According to Theorem 9.4 (Ahuja page 309) we have reached an optimal
493+ * solution if we are able to find a potential and flow that satisfy the
494+ * slackness optimality conditions:
495+ *
496+ * if cost_reduced[i,j] > 0 then x[i,j] = 0
497+ * if 0 < x[i,j] < u[i,j] then cost_reduced[i,j] = 0
498+ * if cost_reduced[i,j] < 0 then x[i,j] = u[i,j]
499+ *
500+ * In our representation the slackness optimality conditions are equivalent
501+ * to the following condition in the residual network:
502+ *
503+ * cost_reduced[i,j] < 0 then capacity[i,j] = 0
504+ *
505+ * Therefore yes, the solution is optimal.
506+ *
507+ * Q. Why is this useful?
508+ *
509+ * A. It can be used to compute a MCF from scratch or build an optimal
510+ * solution starting from a non-optimal one, eg. if we first test the
511+ * solution feasibility we already have a solution canditate, we use that
512+ * flow as input to this function, in another example we might have an
513+ * algorithm that changes the cost function at every iteration and we need
514+ * to find the MCF every time.
515+ * */
516+ static bool mcf_refinement (const tal_t * ctx ,
517+ const struct graph * graph ,
518+ s64 * excess ,
519+ s64 * capacity ,
520+ const s64 * cost ,
521+ s64 * potential )
522+ {
523+ bool solved = false;
524+ tal_t * this_ctx = tal (ctx , tal_t );
525+
526+ if (!this_ctx )
527+ /* bad allocation */
528+ goto finish ;
529+
530+ assert (graph );
531+ assert (excess );
532+ assert (capacity );
533+ assert (cost );
534+ assert (potential );
535+
536+ const size_t max_num_arcs = graph_max_num_arcs (graph );
537+ const size_t max_num_nodes = graph_max_num_nodes (graph );
538+
539+ assert (tal_count (excess ) == max_num_nodes );
540+ assert (tal_count (capacity ) == max_num_arcs );
541+ assert (tal_count (cost ) == max_num_arcs );
542+ assert (tal_count (potential ) == max_num_nodes );
543+
544+ s64 total_excess = 0 ;
545+ for (u32 i = 0 ; i < max_num_nodes ; i ++ )
546+ total_excess += excess [i ];
547+
548+ if (total_excess )
549+ /* there is no way to satisfy the constraints if supply does not
550+ * match demand */
551+ goto finish ;
552+
553+ /* Enforce the complementary slackness condition, rolls back
554+ * constraints. */
555+ for (u32 arc_id = 0 ; arc_id < max_num_arcs ; arc_id ++ ) {
556+ struct arc arc = {.idx = arc_id };
557+ const s64 r = capacity [arc .idx ];
558+
559+ if (reduced_cost (graph , arc , cost , potential ) < 0 && r > 0 ) {
560+ /* This arc's reduced cost is negative and non
561+ * saturated. */
562+ sendflow (graph , arc , r , capacity , excess );
563+ }
564+ }
565+
566+ struct arc * prev = tal_arr (this_ctx , struct arc , max_num_nodes );
567+ s64 * distance = tal_arrz (this_ctx , s64 , max_num_nodes );
568+ if (!prev || !distance )
569+ goto finish ;
570+
571+ /* Now build back constraints again keeping the complementary slackness
572+ * condition. */
573+ for (u32 node_id = 0 ; node_id < max_num_nodes ; node_id ++ ) {
574+ struct node src = {.idx = node_id };
575+
576+ /* is this node a source */
577+ while (excess [src .idx ] > 0 ) {
578+
579+ /* where is the nearest sink */
580+ struct node dst = dijkstra_nearest_sink (
581+ this_ctx , graph , src , excess , capacity , 1 , cost ,
582+ potential , prev , distance );
583+
584+ if (dst .idx >= max_num_nodes )
585+ /* we failed to find a reacheable sink */
586+ goto finish ;
587+
588+ /* traverse the path and see how much flow we can send
589+ */
590+ s64 delta = get_augmenting_flow (graph , src , dst ,
591+ capacity , prev );
592+
593+ delta = MIN (excess [src .idx ], delta );
594+ delta = MIN (- excess [dst .idx ], delta );
595+ assert (delta > 0 );
596+
597+ /* commit that flow to the path */
598+ augment_flow (graph , src , dst , prev , excess , capacity ,
599+ delta );
600+
601+ /* update potentials */
602+ for (u32 n = 0 ; n < max_num_nodes ; n ++ ) {
603+ /* see page 323 of Ahuja-Magnanti-Orlin.
604+ * Whether we prune or not the Dijkstra search,
605+ * the following potentials will keep reduced
606+ * costs non-negative. */
607+ potential [n ] -=
608+ MIN (distance [dst .idx ], distance [n ]);
609+ }
610+ }
611+ }
612+
613+ #ifndef NDEBUG
614+ /* verify that we have satisfied all constraints */
615+ for (u32 i = 0 ; i < max_num_nodes ; i ++ ) {
616+ assert (excess [i ] == 0 );
617+ }
618+ #endif
619+
620+ finish :
621+ tal_free (this_ctx );
622+ return solved ;
623+ }
344624
345625bool simple_mcf (const tal_t * ctx , const struct graph * graph ,
346626 const struct node source , const struct node destination ,
347627 s64 * capacity , s64 amount , const s64 * cost )
348628{
349629 tal_t * this_ctx = tal (ctx , tal_t );
630+ if (!this_ctx )
631+ /* bad allocation */
632+ goto fail ;
633+
634+ if (!graph )
635+ goto fail ;
636+
350637 const size_t max_num_arcs = graph_max_num_arcs (graph );
351638 const size_t max_num_nodes = graph_max_num_nodes (graph );
352- s64 remaining_amount = amount ;
353-
354- if (amount < 0 )
355- goto finish ;
356639
357- if (! graph || source .idx >= max_num_nodes ||
640+ if (amount < 0 || source .idx >= max_num_nodes ||
358641 destination .idx >= max_num_nodes || !capacity || !cost )
359- goto finish ;
642+ goto fail ;
360643
361644 if (tal_count (capacity ) != max_num_arcs ||
362645 tal_count (cost ) != max_num_arcs )
363- goto finish ;
646+ goto fail ;
364647
365- struct arc * prev = tal_arr (this_ctx , struct arc , max_num_nodes );
366- s64 * distance = tal_arrz (this_ctx , s64 , max_num_nodes );
367648 s64 * potential = tal_arrz (this_ctx , s64 , max_num_nodes );
649+ s64 * excess = tal_arrz (this_ctx , s64 , max_num_nodes );
368650
369- if (!prev || !distance || !potential )
370- goto finish ;
651+ if (!potential || !excess )
652+ /* bad allocation */
653+ goto fail ;
371654
372- /* FIXME: implement this algorithm as a search for matching negative and
373- * positive balance nodes, so that we can use it to adapt a flow
374- * structure for changes in the cost function. */
375- while (remaining_amount > 0 ) {
376- if (!dijkstra_path (this_ctx , graph , source , destination ,
377- /* prune = */ true, capacity , 1 , cost ,
378- potential , prev , distance ))
379- goto finish ;
655+ excess [source .idx ] = amount ;
656+ excess [destination .idx ] = - amount ;
380657
381- /* traverse the path and see how much flow we can send */
382- s64 delta = get_augmenting_flow (graph , source , destination ,
383- capacity , prev );
658+ if (!mcf_refinement (this_ctx , graph , excess , capacity , cost , potential ))
659+ goto fail ;
384660
385- /* commit that flow to the path */
386- delta = MIN (remaining_amount , delta );
387- assert (delta > 0 && delta <= remaining_amount );
661+ tal_free (this_ctx );
662+ return true;
388663
389- augment_flow (graph , source , destination , prev , NULL , capacity ,
390- delta );
391- remaining_amount -= delta ;
392-
393- /* update potentials */
394- for (u32 n = 0 ; n < max_num_nodes ; n ++ ) {
395- /* see page 323 of Ahuja-Magnanti-Orlin.
396- * Whether we prune or not the Dijkstra search, the
397- * following potentials will keep reduced costs
398- * non-negative. */
399- potential [n ] -=
400- MIN (distance [destination .idx ], distance [n ]);
401- }
402- }
403- finish :
664+ fail :
404665 tal_free (this_ctx );
405- return remaining_amount == 0 ;
666+ return false ;
406667}
407668
408669s64 flow_cost (const struct graph * graph , const s64 * capacity , const s64 * cost )
0 commit comments