Skip to content

Commit d9ac65e

Browse files
committed
askrene: add a simple MCF solver
Changelog-EXPERIMENTAL: askrene: add a simple MCF solver Signed-off-by: Lagrang3 <[email protected]>
1 parent e377a9a commit d9ac65e

File tree

4 files changed

+191
-1
lines changed

4 files changed

+191
-1
lines changed

plugins/askrene/algorithm.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,87 @@ s64 node_balance(const struct graph *graph,
322322
}
323323
return balance;
324324
}
325+
326+
327+
bool simple_mcf(const tal_t *ctx, const struct graph *graph,
328+
const struct node source, const struct node destination,
329+
s64 *capacity, s64 amount, const s64 *cost)
330+
{
331+
tal_t *this_ctx = tal(ctx, tal_t);
332+
const size_t max_num_arcs = graph_max_num_arcs(graph);
333+
const size_t max_num_nodes = graph_max_num_nodes(graph);
334+
s64 remaining_amount = amount;
335+
336+
if (amount < 0)
337+
goto finish;
338+
339+
if (!graph || source.idx >= max_num_nodes ||
340+
destination.idx >= max_num_nodes || !capacity || !cost)
341+
goto finish;
342+
343+
if (tal_count(capacity) != max_num_arcs ||
344+
tal_count(cost) != max_num_arcs)
345+
goto finish;
346+
347+
struct arc *prev = tal_arr(this_ctx, struct arc, max_num_nodes);
348+
s64 *distance = tal_arrz(this_ctx, s64, max_num_nodes);
349+
s64 *potential = tal_arrz(this_ctx, s64, max_num_nodes);
350+
351+
if (!prev || !distance || !potential)
352+
goto finish;
353+
354+
/* FIXME: implement this algorithm as a search for matching negative and
355+
* positive balance nodes, so that we can use it to adapt a flow
356+
* structure for changes in the cost function. */
357+
while (remaining_amount > 0) {
358+
if (!dijkstra_path(this_ctx, graph, source, destination,
359+
/* prune = */ true, capacity, 1, cost,
360+
potential, prev, distance))
361+
goto finish;
362+
363+
/* traverse the path and see how much flow we can send */
364+
s64 delta = get_augmenting_flow(graph, source, destination,
365+
capacity, prev);
366+
367+
/* commit that flow to the path */
368+
delta = MIN(remaining_amount, delta);
369+
assert(delta > 0 && delta <= remaining_amount);
370+
371+
augment_flow(graph, source, destination, prev, capacity, delta);
372+
remaining_amount -= delta;
373+
374+
/* update potentials */
375+
for (u32 n = 0; n < max_num_nodes; n++) {
376+
/* see page 323 of Ahuja-Magnanti-Orlin.
377+
* Whether we prune or not the Dijkstra search, the
378+
* following potentials will keep reduced costs
379+
* non-negative. */
380+
potential[n] -=
381+
MIN(distance[destination.idx], distance[n]);
382+
}
383+
}
384+
finish:
385+
tal_free(this_ctx);
386+
return remaining_amount == 0;
387+
}
388+
389+
s64 flow_cost(const struct graph *graph, const s64 *capacity, const s64 *cost)
390+
{
391+
const size_t max_num_arcs = graph_max_num_arcs(graph);
392+
s64 total_cost = 0;
393+
394+
assert(graph && capacity && cost);
395+
assert(tal_count(capacity) == max_num_arcs &&
396+
tal_count(cost) == max_num_arcs);
397+
398+
for (u32 i = 0; i < max_num_arcs; i++) {
399+
struct arc arc = {.idx = i};
400+
struct arc dual = arc_dual(graph, arc);
401+
402+
if (arc_is_dual(graph, arc))
403+
continue;
404+
405+
total_cost += capacity[dual.idx] * cost[arc.idx];
406+
}
407+
return total_cost;
408+
}

plugins/askrene/algorithm.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,42 @@ bool simple_feasibleflow(const tal_t *ctx, const struct graph *graph,
115115
s64 node_balance(const struct graph *graph, const struct node node,
116116
const s64 *capacity);
117117

118+
119+
/* Finds the minimum cost flow that satisfy the capacity constraints:
120+
* flow[i] <= capacity[i]
121+
* and supply/demand constraints:
122+
* supply[source] = demand[destination] = amount
123+
* supply/demand[node] = 0 for every other node
124+
*
125+
* It uses successive shortest path algorithm.
126+
*
127+
* input:
128+
* @ctx: tal context for internal allocation
129+
* @graph: topological information of the graph
130+
* @source: source node
131+
* @destination: destination node
132+
* @capacity: arcs capacity
133+
* @amount: desired balance at the destination
134+
* @cost: cost per unit of flow
135+
*
136+
* output:
137+
* @capacity: residual capacity
138+
* returns true if the balance constraint can be satisfied
139+
*
140+
* precondition:
141+
* |capacity|=graph_max_num_arcs
142+
* |cost|=graph_max_num_arcs
143+
* amount>=0
144+
* */
145+
bool simple_mcf(const tal_t *ctx, const struct graph *graph,
146+
const struct node source, const struct node destination,
147+
s64 *capacity, s64 amount, const s64 *cost);
148+
149+
/* Compute the cost of a flow in the network.
150+
*
151+
* @graph: network topology
152+
* @capacity: residual capacity (encodes the flow)
153+
* @cost: cost per unit of flow */
154+
s64 flow_cost(const struct graph *graph, const s64 *capacity, const s64 *cost);
155+
118156
#endif /* LIGHTNING_PLUGINS_ASKRENE_ALGORITHM_H */

plugins/askrene/test/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_ASKRENE_SRC)
1010

1111
PLUGIN_ASKRENE_TEST_COMMON_OBJS :=
1212

13-
plugins/askrene/test/run-bfs plugins/askrene/test/run-dijkstra plugins/askrene/test/run-flow: \
13+
plugins/askrene/test/run-bfs plugins/askrene/test/run-dijkstra plugins/askrene/test/run-flow plugins/askrene/test/run-mcf: \
1414
plugins/askrene/priorityqueue.o \
1515
plugins/askrene/graph.o
1616

plugins/askrene/test/run-mcf.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include "config.h"
2+
#include <assert.h>
3+
#include <ccan/tal/tal.h>
4+
#include <common/setup.h>
5+
#include <inttypes.h>
6+
#include <plugins/askrene/graph.h>
7+
#include <stdio.h>
8+
9+
#include "../algorithm.c"
10+
11+
#define CHECK(arg) if(!(arg)){fprintf(stderr, "failed CHECK at line %d: %s\n", __LINE__, #arg); abort();}
12+
13+
#define MAX_NODES 256
14+
#define MAX_ARCS 256
15+
#define DUAL_BIT 7
16+
17+
int main(int argc, char *argv[])
18+
{
19+
common_setup(argv[0]);
20+
printf("Allocating a memory context\n");
21+
tal_t *ctx = tal(NULL, tal_t);
22+
assert(ctx);
23+
24+
printf("Allocating a graph\n");
25+
struct graph *graph = graph_new(ctx, MAX_NODES, MAX_ARCS, DUAL_BIT);
26+
assert(graph);
27+
28+
s64 *capacity = tal_arrz(ctx, s64, MAX_ARCS);
29+
s64 *cost = tal_arrz(ctx, s64, MAX_ARCS);
30+
31+
graph_add_arc(graph, arc_obj(0), node_obj(0), node_obj(1));
32+
capacity[0] = 2, cost[0] = 0;
33+
graph_add_arc(graph, arc_obj(1), node_obj(0), node_obj(2));
34+
capacity[1] = 2, cost[1] = 0;
35+
graph_add_arc(graph, arc_obj(2), node_obj(1), node_obj(3));
36+
capacity[2] = 1, cost[2] = 1;
37+
graph_add_arc(graph, arc_obj(3), node_obj(1), node_obj(4));
38+
capacity[3] = 1, cost[3] = 2;
39+
graph_add_arc(graph, arc_obj(4), node_obj(2), node_obj(3));
40+
capacity[4] = 2, cost[4] = 1;
41+
graph_add_arc(graph, arc_obj(5), node_obj(2), node_obj(4));
42+
capacity[5] = 1, cost[5] = 2;
43+
graph_add_arc(graph, arc_obj(6), node_obj(3), node_obj(5));
44+
capacity[6] = 3, cost[6] = 0;
45+
graph_add_arc(graph, arc_obj(7), node_obj(4), node_obj(5));
46+
capacity[7] = 3, cost[7] = 0;
47+
48+
struct node src = {.idx = 0};
49+
struct node dst = {.idx = 5};
50+
51+
bool result = simple_mcf(ctx, graph, src, dst, capacity, 4, cost);
52+
CHECK(result);
53+
54+
CHECK(node_balance(graph, src, capacity) == -4);
55+
CHECK(node_balance(graph, dst, capacity) == 4);
56+
57+
for (u32 i = 1; i < 4; i++)
58+
CHECK(node_balance(graph, node_obj(i), capacity) == 0);
59+
60+
const s64 total_cost = flow_cost(graph, capacity, cost);
61+
printf("best flow cost: %" PRIi64 "\n", total_cost);
62+
CHECK(total_cost == 5);
63+
64+
printf("Freeing memory\n");
65+
ctx = tal_free(ctx);
66+
common_shutdown();
67+
return 0;
68+
}

0 commit comments

Comments
 (0)