diff --git a/plugins/askrene/Makefile b/plugins/askrene/Makefile index 88d04e844..a34442df4 100644 --- a/plugins/askrene/Makefile +++ b/plugins/askrene/Makefile @@ -8,7 +8,8 @@ PLUGIN_ASKRENE_SRC := \ plugins/askrene/refine.c \ plugins/askrene/explain_failure.c \ plugins/askrene/graph.c \ - plugins/askrene/priorityqueue.c + plugins/askrene/priorityqueue.c \ + plugins/askrene/algorithm.c PLUGIN_ASKRENE_HEADER := \ plugins/askrene/askrene.h \ @@ -20,7 +21,8 @@ PLUGIN_ASKRENE_HEADER := \ plugins/askrene/refine.h \ plugins/askrene/explain_failure.h \ plugins/askrene/graph.h \ - plugins/askrene/priorityqueue.h + plugins/askrene/priorityqueue.h \ + plugins/askrene/algorithm.h PLUGIN_ASKRENE_OBJS := $(PLUGIN_ASKRENE_SRC:.c=.o) diff --git a/plugins/askrene/algorithm.c b/plugins/askrene/algorithm.c new file mode 100644 index 000000000..50a4f2565 --- /dev/null +++ b/plugins/askrene/algorithm.c @@ -0,0 +1,79 @@ +#include "config.h" +#include +#include +#include +#include +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +/* Simple queue to traverse the network. */ +struct queue_data { + u32 idx; + struct lqueue_link ql; +}; + +bool BFS_path(const tal_t *ctx, const struct graph *graph, + const struct node source, const struct node destination, + const s64 *capacity, const s64 cap_threshold, struct arc *prev) +{ + tal_t *this_ctx = tal(ctx, tal_t); + bool target_found = false; + 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 (!graph || source.idx >= max_num_nodes || !capacity || !prev) + goto finish; + + if (tal_count(capacity) != max_num_arcs || + tal_count(prev) != max_num_nodes) + goto finish; + + for (size_t i = 0; i < max_num_nodes; i++) + prev[i].idx = INVALID_INDEX; + + LQUEUE(struct queue_data, ql) myqueue = LQUEUE_INIT; + struct queue_data *qdata; + + qdata = tal(this_ctx, struct queue_data); + qdata->idx = source.idx; + lqueue_enqueue(&myqueue, qdata); + + while (!lqueue_empty(&myqueue)) { + qdata = lqueue_dequeue(&myqueue); + struct node cur = {.idx = qdata->idx}; + + tal_free(qdata); + + if (cur.idx == destination.idx) { + target_found = true; + break; + } + + for (struct arc arc = node_adjacency_begin(graph, cur); + !node_adjacency_end(arc); + arc = node_adjacency_next(graph, arc)) { + /* check if this arc is traversable */ + if (capacity[arc.idx] < cap_threshold) + continue; + + const struct node next = arc_head(graph, arc); + + /* if that node has been seen previously */ + if (prev[next.idx].idx != INVALID_INDEX) + continue; + + prev[next.idx] = arc; + + qdata = tal(this_ctx, struct queue_data); + qdata->idx = next.idx; + lqueue_enqueue(&myqueue, qdata); + } + } + +finish: + tal_free(this_ctx); + return target_found; +} diff --git a/plugins/askrene/algorithm.h b/plugins/askrene/algorithm.h new file mode 100644 index 000000000..750e69163 --- /dev/null +++ b/plugins/askrene/algorithm.h @@ -0,0 +1,36 @@ +#ifndef LIGHTNING_PLUGINS_ASKRENE_ALGORITHM_H +#define LIGHTNING_PLUGINS_ASKRENE_ALGORITHM_H + +/* Implementation of network algorithms: shortests path, minimum cost flow, etc. + */ + +#include "config.h" +#include + +/* Search any path from source to destination using Breadth First Search. + * + * input: + * @ctx: tal allocator, + * @graph: graph of the network, + * @source: source node, + * @destination: destination node, + * @capacity: arcs capacity + * @cap_threshold: an arc i is traversable if capacity[i]>=cap_threshold + * + * output: + * @prev: prev[i] is the arc that leads to node i for an optimal solution, it + * @return: true if the destination node was reached. + * + * precondition: + * |capacity|=graph_max_num_arcs + * |prev|=graph_max_num_nodes + * + * The destination is only used as a stopping condition, if destination is + * passed with an invalid idx then the algorithm will produce a discovery tree + * of all reacheable nodes from the source. + * */ +bool BFS_path(const tal_t *ctx, const struct graph *graph, + const struct node source, const struct node destination, + const s64 *capacity, const s64 cap_threshold, struct arc *prev); + +#endif /* LIGHTNING_PLUGINS_ASKRENE_ALGORITHM_H */ diff --git a/plugins/askrene/graph.h b/plugins/askrene/graph.h index 512f6b5fe..c21b1f7ac 100644 --- a/plugins/askrene/graph.h +++ b/plugins/askrene/graph.h @@ -1,7 +1,7 @@ #ifndef LIGHTNING_PLUGINS_ASKRENE_GRAPH_H #define LIGHTNING_PLUGINS_ASKRENE_GRAPH_H -/* Solves a Minimum Cost Flow with arc selection costs and side constraints. */ +/* Defines a graph data structure. */ #include "config.h" #include diff --git a/plugins/askrene/test/Makefile b/plugins/askrene/test/Makefile index fba210822..ced945fca 100644 --- a/plugins/askrene/test/Makefile +++ b/plugins/askrene/test/Makefile @@ -10,6 +10,10 @@ $(PLUGIN_RENEPAY_TEST_OBJS): $(PLUGIN_ASKRENE_SRC) PLUGIN_ASKRENE_TEST_COMMON_OBJS := +plugins/askrene/test/run-bfs: \ + plugins/askrene/priorityqueue.o \ + plugins/askrene/graph.o + $(PLUGIN_ASKRENE_TEST_PROGRAMS): $(PLUGIN_ASKRENE_TEST_COMMON_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) check-askrene: $(PLUGIN_ASKRENE_TEST_PROGRAMS:%=unittest/%) diff --git a/plugins/askrene/test/run-bfs.c b/plugins/askrene/test/run-bfs.c new file mode 100644 index 000000000..529ff5832 --- /dev/null +++ b/plugins/askrene/test/run-bfs.c @@ -0,0 +1,100 @@ +#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 show(struct graph *graph, struct node node) +{ + printf("Showing node %" PRIu32 "\n", node.idx); + for (struct arc arc = node_adjacency_begin(graph, node); + !node_adjacency_end(arc); arc = node_adjacency_next(graph, arc)) { + printf("arc id: %" PRIu32 ", (%" PRIu32 " -> %" PRIu32 ")\n", + arc.idx, arc_tail(graph, arc).idx, + arc_head(graph, arc).idx); + } + printf("\n"); +} + +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + 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); + struct arc *prev = tal_arr(ctx, struct arc, MAX_NODES); + + 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] = 1; + graph_add_arc(graph, arc_obj(2), node_obj(1), node_obj(6)); + capacity[2] = 1; + graph_add_arc(graph, arc_obj(3), node_obj(2), node_obj(3)); + capacity[3] = 1; + graph_add_arc(graph, arc_obj(4), node_obj(2), node_obj(4)); + capacity[4] = 0; /* disable this arc */ + graph_add_arc(graph, arc_obj(5), node_obj(3), node_obj(4)); + capacity[5] = 1; + graph_add_arc(graph, arc_obj(6), node_obj(3), node_obj(6)); + capacity[6] = 1; + graph_add_arc(graph, arc_obj(7), node_obj(4), node_obj(5)); + capacity[7] = 1; + graph_add_arc(graph, arc_obj(8), node_obj(5), node_obj(6)); + capacity[8] = 1; + + show(graph, node_obj(1)); + show(graph, node_obj(2)); + show(graph, node_obj(3)); + show(graph, node_obj(4)); + show(graph, node_obj(5)); + show(graph, node_obj(6)); + + struct node src = {.idx = 1}; + struct node dst = {.idx = 5}; + + bool result = BFS_path(ctx, graph, src, dst, capacity, 1, prev); + assert(result); + + int pathlen = 0; + int arc_sequence[] = {7, 5, 1}; + int node_sequence[] = {4, 3, 1}; + + printf("path: "); + for (struct node cur = dst; cur.idx != src.idx;) { + struct arc arc = prev[cur.idx]; + printf("node(%" PRIu32 ") arc(%" PRIu32 ") - ", cur.idx, + arc.idx); + cur = arc_tail(graph, arc); + CHECK(pathlen < 3); + CHECK(cur.idx == node_sequence[pathlen]); + CHECK(arc.idx == arc_sequence[pathlen]); + pathlen ++; + } + CHECK(pathlen == 3); + printf("node(%" PRIu32 ") arc(NONE)\n", src.idx); + printf("path length: %d\n", pathlen); + + printf("Freeing memory\n"); + ctx = tal_free(ctx); + + common_shutdown(); + return 0; +} + diff --git a/plugins/askrene/test/run-graph.c b/plugins/askrene/test/run-graph.c index e9644ff07..3c6d4c945 100644 --- a/plugins/askrene/test/run-graph.c +++ b/plugins/askrene/test/run-graph.c @@ -57,5 +57,6 @@ int main(int argc, char *argv[]) printf("Freeing memory\n"); ctx = tal_free(ctx); common_shutdown(); + return 0; } diff --git a/plugins/askrene/test/run-pqueue.c b/plugins/askrene/test/run-pqueue.c index b2fe1d2a1..a6f8e2454 100644 --- a/plugins/askrene/test/run-pqueue.c +++ b/plugins/askrene/test/run-pqueue.c @@ -101,4 +101,5 @@ int main(int argc, char *argv[]) printf("Freeing memory\n"); ctx = tal_free(ctx); common_shutdown(); + return 0; }