diff --git a/plugins/askrene/algorithm.c b/plugins/askrene/algorithm.c index 4b3acc7a8..bea80c80a 100644 --- a/plugins/askrene/algorithm.c +++ b/plugins/askrene/algorithm.c @@ -5,6 +5,8 @@ #include #include +static const s64 INFINITE = INT64_MAX; + #define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MIN(x, y) (((x) < (y)) ? (x) : (y)) @@ -169,3 +171,154 @@ finish: tal_free(this_ctx); return target_found; } + +/* Get the max amount of flow one can send from source to target along the path + * encoded in `prev`. */ +static s64 get_augmenting_flow(const struct graph *graph, + const struct node source, + const struct node target, const s64 *capacity, + const struct arc *prev) +{ + const size_t max_num_nodes = graph_max_num_nodes(graph); + const size_t max_num_arcs = graph_max_num_arcs(graph); + assert(max_num_nodes == tal_count(prev)); + assert(max_num_arcs == tal_count(capacity)); + + /* count the number of arcs in the path */ + int path_length = 0; + s64 flow = INFINITE; + + struct node cur = target; + while (cur.idx != source.idx) { + assert(cur.idx < max_num_nodes); + const struct arc arc = prev[cur.idx]; + assert(arc.idx < max_num_arcs); + flow = MIN(flow, capacity[arc.idx]); + + /* we are traversing in the opposite direction to the flow, + * hence the next node is at the tail of the arc. */ + cur = arc_tail(graph, arc); + + /* We may never have a path exceeds the number of nodes, it this + * happens it means we have an infinite loop. */ + path_length++; + if(path_length >= max_num_nodes){ + flow = -1; + break; + } + } + + assert(flow < INFINITE && flow > 0); + return flow; +} + +/* Augment a `flow` amount along the path defined by `prev`.*/ +static void augment_flow(const struct graph *graph, + const struct node source, + const struct node target, + const struct arc *prev, + s64 *capacity, + s64 flow) +{ + const size_t max_num_nodes = graph_max_num_nodes(graph); + const size_t max_num_arcs = graph_max_num_arcs(graph); + assert(max_num_nodes == tal_count(prev)); + assert(max_num_arcs == tal_count(capacity)); + + struct node cur = target; + /* count the number of arcs in the path */ + int path_length = 0; + + while (cur.idx != source.idx) { + assert(cur.idx < max_num_nodes); + const struct arc arc = prev[cur.idx]; + const struct arc dual = arc_dual(graph, arc); + + assert(arc.idx < max_num_arcs); + assert(dual.idx < max_num_arcs); + + capacity[arc.idx] -= flow; + capacity[dual.idx] += flow; + + assert(capacity[arc.idx] >= 0); + + /* we are traversing in the opposite direction to the flow, + * hence the next node is at the tail of the arc. */ + cur = arc_tail(graph, arc); + + /* We may never have a path exceeds the number of nodes, it this + * happens it means we have an infinite loop. */ + path_length++; + if(path_length >= max_num_nodes) + break; + } + assert(path_length < max_num_nodes); +} + +bool simple_feasibleflow(const tal_t *ctx, + const struct graph *graph, + const struct node source, + const struct node destination, + s64 *capacity, + s64 amount) +{ + tal_t *this_ctx = tal(ctx, tal_t); + const size_t max_num_arcs = graph_max_num_arcs(graph); + const size_t max_num_nodes = graph_max_num_nodes(graph); + + /* check preconditions */ + if (amount < 0) + goto finish; + + if (!graph || source.idx >= max_num_nodes || + destination.idx >= max_num_nodes || !capacity) + goto finish; + + if (tal_count(capacity) != max_num_arcs) + goto finish; + + /* path information + * prev: is the id of the arc that lead to the node. */ + struct arc *prev = tal_arr(this_ctx, struct arc, max_num_nodes); + if (!prev) + goto finish; + + while (amount > 0) { + /* find a path from source to target */ + if (!BFS_path(this_ctx, graph, source, destination, capacity, 1, + prev)) + goto finish; + + /* traverse the path and see how much flow we can send */ + s64 delta = get_augmenting_flow(graph, source, destination, + capacity, prev); + + /* commit that flow to the path */ + delta = MIN(amount, delta); + assert(delta > 0 && delta <= amount); + + augment_flow(graph, source, destination, prev, capacity, delta); + amount -= delta; + } +finish: + tal_free(this_ctx); + return amount == 0; +} + +s64 node_balance(const struct graph *graph, + const struct node node, + const s64 *capacity) +{ + s64 balance = 0; + + for (struct arc arc = node_adjacency_begin(graph, node); + !node_adjacency_end(arc); arc = node_adjacency_next(graph, arc)) { + struct arc dual = arc_dual(graph, arc); + + if (arc_is_dual(graph, arc)) + balance += capacity[arc.idx]; + else + balance -= capacity[dual.idx]; + } + return balance; +} diff --git a/plugins/askrene/algorithm.h b/plugins/askrene/algorithm.h index ec571314c..5f13107cb 100644 --- a/plugins/askrene/algorithm.h +++ b/plugins/askrene/algorithm.h @@ -69,4 +69,50 @@ bool dijkstra_path(const tal_t *ctx, const struct graph *graph, s64 *distance); +/* Finds any flow that satisfy the capacity constraints: + * flow[i] <= capacity[i] + * and supply/demand constraints: + * supply[source] = demand[destination] = amount + * supply/demand[node] = 0 for every other node + * + * It uses simple augmenting paths algorithm. + * + * input: + * @ctx: tal context for internal allocation + * @graph: topological information of the graph + * @source: source node + * @destination: destination node + * @capacity: arcs capacity + * @amount: supply/demand + * + * output: + * @capacity: residual capacity + * returns true if the balance constraint can be satisfied + * + * precondition: + * |capacity|=graph_max_num_arcs + * amount>=0 + * */ +bool simple_feasibleflow(const tal_t *ctx, const struct graph *graph, + const struct node source, + const struct node destination, s64 *capacity, + s64 amount); + + +/* Computes the balance of a node, ie. the incoming flows minus the outgoing. + * + * @graph: topology + * @node: node + * @capacity: capacity in the residual sense, not the constrain capacity + * + * This works because in the adjacency list an arc wich is dual is associated + * with an inconming arc i, then we add this flow, while an arc which is not + * dual corresponds to and outgoing flow that we need to substract. + * The flow on the arc i (not dual) is computed as: + * flow[i] = residual_capacity[i_dual], + * while the constrain capacity is + * capacity[i] = residual_capacity[i] + residual_capacity[i_dual] */ +s64 node_balance(const struct graph *graph, const struct node node, + const s64 *capacity); + #endif /* LIGHTNING_PLUGINS_ASKRENE_ALGORITHM_H */ diff --git a/plugins/askrene/test/Makefile b/plugins/askrene/test/Makefile index 1e1231949..6b414fc62 100644 --- a/plugins/askrene/test/Makefile +++ b/plugins/askrene/test/Makefile @@ -10,7 +10,7 @@ $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_ASKRENE_SRC) PLUGIN_ASKRENE_TEST_COMMON_OBJS := -plugins/askrene/test/run-bfs plugins/askrene/test/run-dijkstra: \ +plugins/askrene/test/run-bfs plugins/askrene/test/run-dijkstra plugins/askrene/test/run-flow: \ plugins/askrene/priorityqueue.o \ plugins/askrene/graph.o diff --git a/plugins/askrene/test/run-flow.c b/plugins/askrene/test/run-flow.c new file mode 100644 index 000000000..ead35db7c --- /dev/null +++ b/plugins/askrene/test/run-flow.c @@ -0,0 +1,113 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include + +#include "../algorithm.c" + +#define MAX_NODES 256 +#define MAX_ARCS 256 +#define DUAL_BIT 7 + +#define CHECK(arg) if(!(arg)){fprintf(stderr, "failed CHECK at line %d: %s\n", __LINE__, #arg); abort();} + +static void problem1(void){ + printf("Allocating a memory context\n"); + tal_t *ctx = tal(NULL, tal_t); + assert(ctx); + + printf("Allocating a graph\n"); + struct graph *graph = graph_new(ctx, MAX_NODES, MAX_ARCS, DUAL_BIT); + assert(graph); + + s64 *capacity = tal_arrz(ctx, s64, MAX_ARCS); + + graph_add_arc(graph, arc_obj(0), node_obj(1), node_obj(2)); + capacity[0] = 1; + graph_add_arc(graph, arc_obj(1), node_obj(1), node_obj(3)); + capacity[1] = 4; + graph_add_arc(graph, arc_obj(2), node_obj(2), node_obj(4)); + capacity[2] = 1; + graph_add_arc(graph, arc_obj(3), node_obj(2), node_obj(5)); + capacity[3] = 1; + graph_add_arc(graph, arc_obj(4), node_obj(3), node_obj(5)); + capacity[4] = 4; + graph_add_arc(graph, arc_obj(5), node_obj(4), node_obj(6)); + capacity[5] = 1; + graph_add_arc(graph, arc_obj(6), node_obj(6), node_obj(10)); + capacity[6] = 1; + graph_add_arc(graph, arc_obj(7), node_obj(5), node_obj(10)); + capacity[7] = 4; + + struct node src = {.idx = 1}; + struct node dst = {.idx = 10}; + + bool result = simple_feasibleflow(ctx, graph, src, dst, capacity, 5); + CHECK(result); + + CHECK(node_balance(graph, src, capacity) == -5); + CHECK(node_balance(graph, dst, capacity) == 5); + + for (u32 i = 2; i < 10; i++) + CHECK(node_balance(graph, node_obj(i), capacity) == 0); + + printf("Freeing memory\n"); + ctx = tal_free(ctx); +} + +static void problem2(void){ + /* Stress the graph constraints by setting max_num_nodes to exactly the + * number of node that participate and put all nodes in line to achieve + * the largest path length possible. */ + printf("Allocating a memory context\n"); + tal_t *ctx = tal(NULL, tal_t); + assert(ctx); + + printf("Allocating a graph\n"); + struct graph *graph = graph_new(ctx, 5, MAX_ARCS, DUAL_BIT); + assert(graph); + + s64 *capacity = tal_arrz(ctx, s64, MAX_ARCS); + + graph_add_arc(graph, arc_obj(0), node_obj(0), node_obj(1)); + capacity[0] = 1; + graph_add_arc(graph, arc_obj(1), node_obj(1), node_obj(2)); + capacity[1] = 4; + graph_add_arc(graph, arc_obj(2), node_obj(2), node_obj(3)); + capacity[2] = 1; + graph_add_arc(graph, arc_obj(3), node_obj(3), node_obj(4)); + capacity[3] = 1; + + struct node src = {.idx = 0}; + struct node dst = {.idx = 4}; + + bool result = simple_feasibleflow(ctx, graph, src, dst, capacity, 1); + CHECK(result); + + CHECK(node_balance(graph, src, capacity) == -1); + CHECK(node_balance(graph, dst, capacity) == 1); + + for (u32 i = 1; i < 4; i++) + CHECK(node_balance(graph, node_obj(i), capacity) == 0); + + printf("Freeing memory\n"); + ctx = tal_free(ctx); +} + +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + + printf("\n\nProblem 1\n\n"); + problem1(); + + printf("\n\nProblem 2\n\n"); + problem2(); + + common_shutdown(); + return 0; +} +