Skip to content

Commit e377a9a

Browse files
committed
askrene: add algorithm to compute feasible flow
Changelog-EXPERIMENTAL: askrene: add algorithm to compute feasible flow Signed-off-by: Lagrang3 <[email protected]>
1 parent dda4704 commit e377a9a

File tree

4 files changed

+313
-1
lines changed

4 files changed

+313
-1
lines changed

plugins/askrene/algorithm.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <plugins/askrene/algorithm.h>
66
#include <plugins/askrene/priorityqueue.h>
77

8+
static const s64 INFINITE = INT64_MAX;
9+
810
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
911
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
1012

@@ -169,3 +171,154 @@ bool dijkstra_path(const tal_t *ctx, const struct graph *graph,
169171
tal_free(this_ctx);
170172
return target_found;
171173
}
174+
175+
/* Get the max amount of flow one can send from source to target along the path
176+
* encoded in `prev`. */
177+
static s64 get_augmenting_flow(const struct graph *graph,
178+
const struct node source,
179+
const struct node target, const s64 *capacity,
180+
const struct arc *prev)
181+
{
182+
const size_t max_num_nodes = graph_max_num_nodes(graph);
183+
const size_t max_num_arcs = graph_max_num_arcs(graph);
184+
assert(max_num_nodes == tal_count(prev));
185+
assert(max_num_arcs == tal_count(capacity));
186+
187+
/* count the number of arcs in the path */
188+
int path_length = 0;
189+
s64 flow = INFINITE;
190+
191+
struct node cur = target;
192+
while (cur.idx != source.idx) {
193+
assert(cur.idx < max_num_nodes);
194+
const struct arc arc = prev[cur.idx];
195+
assert(arc.idx < max_num_arcs);
196+
flow = MIN(flow, capacity[arc.idx]);
197+
198+
/* we are traversing in the opposite direction to the flow,
199+
* hence the next node is at the tail of the arc. */
200+
cur = arc_tail(graph, arc);
201+
202+
/* We may never have a path exceeds the number of nodes, it this
203+
* happens it means we have an infinite loop. */
204+
path_length++;
205+
if(path_length >= max_num_nodes){
206+
flow = -1;
207+
break;
208+
}
209+
}
210+
211+
assert(flow < INFINITE && flow > 0);
212+
return flow;
213+
}
214+
215+
/* Augment a `flow` amount along the path defined by `prev`.*/
216+
static void augment_flow(const struct graph *graph,
217+
const struct node source,
218+
const struct node target,
219+
const struct arc *prev,
220+
s64 *capacity,
221+
s64 flow)
222+
{
223+
const size_t max_num_nodes = graph_max_num_nodes(graph);
224+
const size_t max_num_arcs = graph_max_num_arcs(graph);
225+
assert(max_num_nodes == tal_count(prev));
226+
assert(max_num_arcs == tal_count(capacity));
227+
228+
struct node cur = target;
229+
/* count the number of arcs in the path */
230+
int path_length = 0;
231+
232+
while (cur.idx != source.idx) {
233+
assert(cur.idx < max_num_nodes);
234+
const struct arc arc = prev[cur.idx];
235+
const struct arc dual = arc_dual(graph, arc);
236+
237+
assert(arc.idx < max_num_arcs);
238+
assert(dual.idx < max_num_arcs);
239+
240+
capacity[arc.idx] -= flow;
241+
capacity[dual.idx] += flow;
242+
243+
assert(capacity[arc.idx] >= 0);
244+
245+
/* we are traversing in the opposite direction to the flow,
246+
* hence the next node is at the tail of the arc. */
247+
cur = arc_tail(graph, arc);
248+
249+
/* We may never have a path exceeds the number of nodes, it this
250+
* happens it means we have an infinite loop. */
251+
path_length++;
252+
if(path_length >= max_num_nodes)
253+
break;
254+
}
255+
assert(path_length < max_num_nodes);
256+
}
257+
258+
bool simple_feasibleflow(const tal_t *ctx,
259+
const struct graph *graph,
260+
const struct node source,
261+
const struct node destination,
262+
s64 *capacity,
263+
s64 amount)
264+
{
265+
tal_t *this_ctx = tal(ctx, tal_t);
266+
const size_t max_num_arcs = graph_max_num_arcs(graph);
267+
const size_t max_num_nodes = graph_max_num_nodes(graph);
268+
269+
/* check preconditions */
270+
if (amount < 0)
271+
goto finish;
272+
273+
if (!graph || source.idx >= max_num_nodes ||
274+
destination.idx >= max_num_nodes || !capacity)
275+
goto finish;
276+
277+
if (tal_count(capacity) != max_num_arcs)
278+
goto finish;
279+
280+
/* path information
281+
* prev: is the id of the arc that lead to the node. */
282+
struct arc *prev = tal_arr(this_ctx, struct arc, max_num_nodes);
283+
if (!prev)
284+
goto finish;
285+
286+
while (amount > 0) {
287+
/* find a path from source to target */
288+
if (!BFS_path(this_ctx, graph, source, destination, capacity, 1,
289+
prev))
290+
goto finish;
291+
292+
/* traverse the path and see how much flow we can send */
293+
s64 delta = get_augmenting_flow(graph, source, destination,
294+
capacity, prev);
295+
296+
/* commit that flow to the path */
297+
delta = MIN(amount, delta);
298+
assert(delta > 0 && delta <= amount);
299+
300+
augment_flow(graph, source, destination, prev, capacity, delta);
301+
amount -= delta;
302+
}
303+
finish:
304+
tal_free(this_ctx);
305+
return amount == 0;
306+
}
307+
308+
s64 node_balance(const struct graph *graph,
309+
const struct node node,
310+
const s64 *capacity)
311+
{
312+
s64 balance = 0;
313+
314+
for (struct arc arc = node_adjacency_begin(graph, node);
315+
!node_adjacency_end(arc); arc = node_adjacency_next(graph, arc)) {
316+
struct arc dual = arc_dual(graph, arc);
317+
318+
if (arc_is_dual(graph, arc))
319+
balance += capacity[arc.idx];
320+
else
321+
balance -= capacity[dual.idx];
322+
}
323+
return balance;
324+
}

plugins/askrene/algorithm.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,50 @@ bool dijkstra_path(const tal_t *ctx, const struct graph *graph,
6969
s64 *distance);
7070

7171

72+
/* Finds any flow that satisfy the capacity constraints:
73+
* flow[i] <= capacity[i]
74+
* and supply/demand constraints:
75+
* supply[source] = demand[destination] = amount
76+
* supply/demand[node] = 0 for every other node
77+
*
78+
* It uses simple augmenting paths algorithm.
79+
*
80+
* input:
81+
* @ctx: tal context for internal allocation
82+
* @graph: topological information of the graph
83+
* @source: source node
84+
* @destination: destination node
85+
* @capacity: arcs capacity
86+
* @amount: supply/demand
87+
*
88+
* output:
89+
* @capacity: residual capacity
90+
* returns true if the balance constraint can be satisfied
91+
*
92+
* precondition:
93+
* |capacity|=graph_max_num_arcs
94+
* amount>=0
95+
* */
96+
bool simple_feasibleflow(const tal_t *ctx, const struct graph *graph,
97+
const struct node source,
98+
const struct node destination, s64 *capacity,
99+
s64 amount);
100+
101+
102+
/* Computes the balance of a node, ie. the incoming flows minus the outgoing.
103+
*
104+
* @graph: topology
105+
* @node: node
106+
* @capacity: capacity in the residual sense, not the constrain capacity
107+
*
108+
* This works because in the adjacency list an arc wich is dual is associated
109+
* with an inconming arc i, then we add this flow, while an arc which is not
110+
* dual corresponds to and outgoing flow that we need to substract.
111+
* The flow on the arc i (not dual) is computed as:
112+
* flow[i] = residual_capacity[i_dual],
113+
* while the constrain capacity is
114+
* capacity[i] = residual_capacity[i] + residual_capacity[i_dual] */
115+
s64 node_balance(const struct graph *graph, const struct node node,
116+
const s64 *capacity);
117+
72118
#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: \
13+
plugins/askrene/test/run-bfs plugins/askrene/test/run-dijkstra plugins/askrene/test/run-flow: \
1414
plugins/askrene/priorityqueue.o \
1515
plugins/askrene/graph.o
1616

plugins/askrene/test/run-flow.c

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 MAX_NODES 256
12+
#define MAX_ARCS 256
13+
#define DUAL_BIT 7
14+
15+
#define CHECK(arg) if(!(arg)){fprintf(stderr, "failed CHECK at line %d: %s\n", __LINE__, #arg); abort();}
16+
17+
static void problem1(void){
18+
printf("Allocating a memory context\n");
19+
tal_t *ctx = tal(NULL, tal_t);
20+
assert(ctx);
21+
22+
printf("Allocating a graph\n");
23+
struct graph *graph = graph_new(ctx, MAX_NODES, MAX_ARCS, DUAL_BIT);
24+
assert(graph);
25+
26+
s64 *capacity = tal_arrz(ctx, s64, MAX_ARCS);
27+
28+
graph_add_arc(graph, arc_obj(0), node_obj(1), node_obj(2));
29+
capacity[0] = 1;
30+
graph_add_arc(graph, arc_obj(1), node_obj(1), node_obj(3));
31+
capacity[1] = 4;
32+
graph_add_arc(graph, arc_obj(2), node_obj(2), node_obj(4));
33+
capacity[2] = 1;
34+
graph_add_arc(graph, arc_obj(3), node_obj(2), node_obj(5));
35+
capacity[3] = 1;
36+
graph_add_arc(graph, arc_obj(4), node_obj(3), node_obj(5));
37+
capacity[4] = 4;
38+
graph_add_arc(graph, arc_obj(5), node_obj(4), node_obj(6));
39+
capacity[5] = 1;
40+
graph_add_arc(graph, arc_obj(6), node_obj(6), node_obj(10));
41+
capacity[6] = 1;
42+
graph_add_arc(graph, arc_obj(7), node_obj(5), node_obj(10));
43+
capacity[7] = 4;
44+
45+
struct node src = {.idx = 1};
46+
struct node dst = {.idx = 10};
47+
48+
bool result = simple_feasibleflow(ctx, graph, src, dst, capacity, 5);
49+
CHECK(result);
50+
51+
CHECK(node_balance(graph, src, capacity) == -5);
52+
CHECK(node_balance(graph, dst, capacity) == 5);
53+
54+
for (u32 i = 2; i < 10; i++)
55+
CHECK(node_balance(graph, node_obj(i), capacity) == 0);
56+
57+
printf("Freeing memory\n");
58+
ctx = tal_free(ctx);
59+
}
60+
61+
static void problem2(void){
62+
/* Stress the graph constraints by setting max_num_nodes to exactly the
63+
* number of node that participate and put all nodes in line to achieve
64+
* the largest path length possible. */
65+
printf("Allocating a memory context\n");
66+
tal_t *ctx = tal(NULL, tal_t);
67+
assert(ctx);
68+
69+
printf("Allocating a graph\n");
70+
struct graph *graph = graph_new(ctx, 5, MAX_ARCS, DUAL_BIT);
71+
assert(graph);
72+
73+
s64 *capacity = tal_arrz(ctx, s64, MAX_ARCS);
74+
75+
graph_add_arc(graph, arc_obj(0), node_obj(0), node_obj(1));
76+
capacity[0] = 1;
77+
graph_add_arc(graph, arc_obj(1), node_obj(1), node_obj(2));
78+
capacity[1] = 4;
79+
graph_add_arc(graph, arc_obj(2), node_obj(2), node_obj(3));
80+
capacity[2] = 1;
81+
graph_add_arc(graph, arc_obj(3), node_obj(3), node_obj(4));
82+
capacity[3] = 1;
83+
84+
struct node src = {.idx = 0};
85+
struct node dst = {.idx = 4};
86+
87+
bool result = simple_feasibleflow(ctx, graph, src, dst, capacity, 1);
88+
CHECK(result);
89+
90+
CHECK(node_balance(graph, src, capacity) == -1);
91+
CHECK(node_balance(graph, dst, capacity) == 1);
92+
93+
for (u32 i = 1; i < 4; i++)
94+
CHECK(node_balance(graph, node_obj(i), capacity) == 0);
95+
96+
printf("Freeing memory\n");
97+
ctx = tal_free(ctx);
98+
}
99+
100+
int main(int argc, char *argv[])
101+
{
102+
common_setup(argv[0]);
103+
104+
printf("\n\nProblem 1\n\n");
105+
problem1();
106+
107+
printf("\n\nProblem 2\n\n");
108+
problem2();
109+
110+
common_shutdown();
111+
return 0;
112+
}
113+

0 commit comments

Comments
 (0)