From: Adrián Arroyo Calle Date: Fri, 31 Dec 2021 11:56:24 +0000 (+0100) Subject: Add ugraphs library X-Git-Tag: v0.9.0^2~84^2~2 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=0288d5dc1950c55535c080a43a3ba04facd4fa2b;p=scryer-prolog.git Add ugraphs library --- diff --git a/README.md b/README.md index 09878933..2f4a812d 100644 --- a/README.md +++ b/README.md @@ -552,6 +552,7 @@ The modules that ship with Scryer Prolog are also called * [`uuid`](src/lib/uuid.pl) UUIDv4 generation and hex representation * [`tls`](src/lib/tls.pl) Predicates for negotiating TLS connections explicitly. +* [`ugraphs`](src/lib/ugraphs.pl) Graph manipulation library To use predicates provided by the `lists` library, write: diff --git a/src/lib/ugraphs.pl b/src/lib/ugraphs.pl new file mode 100644 index 00000000..257d4e01 --- /dev/null +++ b/src/lib/ugraphs.pl @@ -0,0 +1,634 @@ +/* Part of SWI-Prolog + + Author: R.A.O'Keefe, Vitor Santos Costa, Jan Wielemaker + E-mail: J.Wielemaker@vu.nl + WWW: http://www.swi-prolog.org + Copyright (c) 1984-2021, VU University Amsterdam + CWI, Amsterdam + SWI-Prolog Solutions .b.v + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +:- module(ugraphs, + [ add_edges/3, % +Graph, +Edges, -NewGraph + add_vertices/3, % +Graph, +Vertices, -NewGraph + complement/2, % +Graph, -NewGraph + compose/3, % +LeftGraph, +RightGraph, -NewGraph + del_edges/3, % +Graph, +Edges, -NewGraph + del_vertices/3, % +Graph, +Vertices, -NewGraph + edges/2, % +Graph, -Edges + neighbors/3, % +Vertex, +Graph, -Vertices + neighbours/3, % +Vertex, +Graph, -Vertices + reachable/3, % +Vertex, +Graph, -Vertices + top_sort/2, % +Graph, -Sort + top_sort/3, % +Graph, -Sort0, -Sort + transitive_closure/2, % +Graph, -Closure + transpose_ugraph/2, % +Graph, -NewGraph + vertices/2, % +Graph, -Vertices + vertices_edges_to_ugraph/3, % +Vertices, +Edges, -Graph + ugraph_union/3, % +Graph1, +Graph2, -Graph + connect_ugraph/3 % +Graph1, -Start, -Graph + ]). + +/** Graph manipulation library + +The S-representation of a graph is a list of (vertex-neighbours) pairs, +where the pairs are in standard order (as produced by keysort) and the +neighbours of each vertex are also in standard order (as produced by +sort). This form is convenient for many calculations. + +A new UGraph from raw data can be created using +vertices_edges_to_ugraph/3. + +Adapted to support some of the functionality of the SICStus ugraphs +library by Vitor Santos Costa. + +Ported from YAP 5.0.1 to SWI-Prolog by Jan Wielemaker. + +@author R.A.O'Keefe +@author Vitor Santos Costa +@author Jan Wielemaker +@license BSD-2 or Artistic 2.0 +*/ + +:- use_module(library(lists)). +:- use_module(library(pairs)). +:- use_module(library(ordsets)). + +%! vertices(+Graph, -Vertices) +% +% Unify Vertices with all vertices appearing in Graph. Example: +% +% ?- vertices([1-[3,5],2-[4],3-[],4-[5],5-[]], L). +% L = [1, 2, 3, 4, 5] + +vertices([], []) :- !. +vertices([Vertex-_|Graph], [Vertex|Vertices]) :- + vertices(Graph, Vertices). + + +%! vertices_edges_to_ugraph(+Vertices, +Edges, -UGraph) is det. +% +% Create a UGraph from Vertices and edges. Given a graph with a +% set of Vertices and a set of Edges, Graph must unify with the +% corresponding S-representation. Note that the vertices without +% edges will appear in Vertices but not in Edges. Moreover, it is +% sufficient for a vertice to appear in Edges. +% +% == +% ?- vertices_edges_to_ugraph([],[1-3,2-4,4-5,1-5], L). +% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[]] +% == +% +% In this case all vertices are defined implicitly. The next +% example shows three unconnected vertices: +% +% == +% ?- vertices_edges_to_ugraph([6,7,8],[1-3,2-4,4-5,1-5], L). +% L = [1-[3,5], 2-[4], 3-[], 4-[5], 5-[], 6-[], 7-[], 8-[]] +% == + +vertices_edges_to_ugraph(Vertices, Edges, Graph) :- + sort(Edges, EdgeSet), + p_to_s_vertices(EdgeSet, IVertexBag), + append(Vertices, IVertexBag, VertexBag), + sort(VertexBag, VertexSet), + p_to_s_group(VertexSet, EdgeSet, Graph). + + +%! add_vertices(+Graph, +Vertices, -NewGraph) +% +% Unify NewGraph with a new graph obtained by adding the list of +% Vertices to Graph. Example: +% +% ``` +% ?- add_vertices([1-[3,5],2-[]], [0,1,2,9], NG). +% NG = [0-[], 1-[3,5], 2-[], 9-[]] +% ``` + +% replace with real msort/2 when available +msort_(List, Sorted) :- + maplist(item_pair, List, Pairs), + keysort(Pairs, SortedPairs), + pairs_keys(SortedPairs, Sorted). + +item_pair(X, X-X). + +add_vertices(Graph, Vertices, NewGraph) :- + % msort/2 not available in Scryer Prolog yet: msort(Vertices, V1), + msort_(Vertices, V1), + add_vertices_to_s_graph(V1, Graph, NewGraph). + +add_vertices_to_s_graph(L, [], NL) :- + !, + add_empty_vertices(L, NL). +add_vertices_to_s_graph([], L, L) :- !. +add_vertices_to_s_graph([V1|VL], [V-Edges|G], NGL) :- + compare(Res, V1, V), + add_vertices_to_s_graph(Res, V1, VL, V, Edges, G, NGL). + +add_vertices_to_s_graph(=, _, VL, V, Edges, G, [V-Edges|NGL]) :- + add_vertices_to_s_graph(VL, G, NGL). +add_vertices_to_s_graph(<, V1, VL, V, Edges, G, [V1-[]|NGL]) :- + add_vertices_to_s_graph(VL, [V-Edges|G], NGL). +add_vertices_to_s_graph(>, V1, VL, V, Edges, G, [V-Edges|NGL]) :- + add_vertices_to_s_graph([V1|VL], G, NGL). + +add_empty_vertices([], []). +add_empty_vertices([V|G], [V-[]|NG]) :- + add_empty_vertices(G, NG). + +%! del_vertices(+Graph, +Vertices, -NewGraph) is det. +% +% Unify NewGraph with a new graph obtained by deleting the list of +% Vertices and all the edges that start from or go to a vertex in +% Vertices to the Graph. Example: +% +% == +% ?- del_vertices([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[2,6],8-[]], +% [2,1], +% NL). +% NL = [3-[],4-[5],5-[],6-[],7-[6],8-[]] +% == +% +% @compat Upto 5.6.48 the argument order was (+Vertices, +Graph, +% -NewGraph). Both YAP and SWI-Prolog have changed the argument +% order for compatibility with recent SICStus as well as +% consistency with del_edges/3. + +del_vertices(Graph, Vertices, NewGraph) :- + sort(Vertices, V1), % JW: was msort + ( V1 = [] + -> Graph = NewGraph + ; del_vertices(Graph, V1, V1, NewGraph) + ). + +del_vertices(G, [], V1, NG) :- + !, + del_remaining_edges_for_vertices(G, V1, NG). +del_vertices([], _, _, []). +del_vertices([V-Edges|G], [V0|Vs], V1, NG) :- + compare(Res, V, V0), + split_on_del_vertices(Res, V,Edges, [V0|Vs], NVs, V1, NG, NGr), + del_vertices(G, NVs, V1, NGr). + +del_remaining_edges_for_vertices([], _, []). +del_remaining_edges_for_vertices([V0-Edges|G], V1, [V0-NEdges|NG]) :- + ord_subtract(Edges, V1, NEdges), + del_remaining_edges_for_vertices(G, V1, NG). + +split_on_del_vertices(<, V, Edges, Vs, Vs, V1, [V-NEdges|NG], NG) :- + ord_subtract(Edges, V1, NEdges). +split_on_del_vertices(>, V, Edges, [_|Vs], Vs, V1, [V-NEdges|NG], NG) :- + ord_subtract(Edges, V1, NEdges). +split_on_del_vertices(=, _, _, [_|Vs], Vs, _, NG, NG). + +%! add_edges(+Graph, +Edges, -NewGraph) +% +% Unify NewGraph with a new graph obtained by adding the list of Edges +% to Graph. Example: +% +% ``` +% ?- add_edges([1-[3,5],2-[4],3-[],4-[5], +% 5-[],6-[],7-[],8-[]], +% [1-6,2-3,3-2,5-7,3-2,4-5], +% NL). +% NL = [1-[3,5,6], 2-[3,4], 3-[2], 4-[5], +% 5-[7], 6-[], 7-[], 8-[]] +% ``` + +add_edges(Graph, Edges, NewGraph) :- + p_to_s_graph(Edges, G1), + ugraph_union(Graph, G1, NewGraph). + +%! ugraph_union(+Graph1, +Graph2, -NewGraph) +% +% NewGraph is the union of Graph1 and Graph2. Example: +% +% ``` +% ?- ugraph_union([1-[2],2-[3]],[2-[4],3-[1,2,4]],L). +% L = [1-[2], 2-[3,4], 3-[1,2,4]] +% ``` + +ugraph_union(Set1, [], Set1) :- !. +ugraph_union([], Set2, Set2) :- !. +ugraph_union([Head1-E1|Tail1], [Head2-E2|Tail2], Union) :- + compare(Order, Head1, Head2), + ugraph_union(Order, Head1-E1, Tail1, Head2-E2, Tail2, Union). + +ugraph_union(=, Head-E1, Tail1, _-E2, Tail2, [Head-Es|Union]) :- + ord_union(E1, E2, Es), + ugraph_union(Tail1, Tail2, Union). +ugraph_union(<, Head1, Tail1, Head2, Tail2, [Head1|Union]) :- + ugraph_union(Tail1, [Head2|Tail2], Union). +ugraph_union(>, Head1, Tail1, Head2, Tail2, [Head2|Union]) :- + ugraph_union([Head1|Tail1], Tail2, Union). + +%! del_edges(+Graph, +Edges, -NewGraph) +% +% Unify NewGraph with a new graph obtained by removing the list of +% Edges from Graph. Notice that no vertices are deleted. Example: +% +% ``` +% ?- del_edges([1-[3,5],2-[4],3-[],4-[5],5-[],6-[],7-[],8-[]], +% [1-6,2-3,3-2,5-7,3-2,4-5,1-3], +% NL). +% NL = [1-[5],2-[4],3-[],4-[],5-[],6-[],7-[],8-[]] +% ``` + +del_edges(Graph, Edges, NewGraph) :- + p_to_s_graph(Edges, G1), + graph_subtract(Graph, G1, NewGraph). + +%! graph_subtract(+Set1, +Set2, ?Difference) +% +% Is based on ord_subtract + +graph_subtract(Set1, [], Set1) :- !. +graph_subtract([], _, []). +graph_subtract([Head1-E1|Tail1], [Head2-E2|Tail2], Difference) :- + compare(Order, Head1, Head2), + graph_subtract(Order, Head1-E1, Tail1, Head2-E2, Tail2, Difference). + +graph_subtract(=, H-E1, Tail1, _-E2, Tail2, [H-E|Difference]) :- + ord_subtract(E1,E2,E), + graph_subtract(Tail1, Tail2, Difference). +graph_subtract(<, Head1, Tail1, Head2, Tail2, [Head1|Difference]) :- + graph_subtract(Tail1, [Head2|Tail2], Difference). +graph_subtract(>, Head1, Tail1, _, Tail2, Difference) :- + graph_subtract([Head1|Tail1], Tail2, Difference). + +%! edges(+Graph, -Edges) +% +% Unify Edges with all edges appearing in Graph. Example: +% +% ?- edges([1-[3,5],2-[4],3-[],4-[5],5-[]], L). +% L = [1-3, 1-5, 2-4, 4-5] + +edges(Graph, Edges) :- + s_to_p_graph(Graph, Edges). + +p_to_s_graph(P_Graph, S_Graph) :- + sort(P_Graph, EdgeSet), + p_to_s_vertices(EdgeSet, VertexBag), + sort(VertexBag, VertexSet), + p_to_s_group(VertexSet, EdgeSet, S_Graph). + + +p_to_s_vertices([], []). +p_to_s_vertices([A-Z|Edges], [A,Z|Vertices]) :- + p_to_s_vertices(Edges, Vertices). + + +p_to_s_group([], _, []). +p_to_s_group([Vertex|Vertices], EdgeSet, [Vertex-Neibs|G]) :- + p_to_s_group(EdgeSet, Vertex, Neibs, RestEdges), + p_to_s_group(Vertices, RestEdges, G). + + +p_to_s_group([V1-X|Edges], V2, [X|Neibs], RestEdges) :- V1 == V2, + !, + p_to_s_group(Edges, V2, Neibs, RestEdges). +p_to_s_group(Edges, _, [], Edges). + + + +s_to_p_graph([], []) :- !. +s_to_p_graph([Vertex-Neibs|G], P_Graph) :- + s_to_p_graph(Neibs, Vertex, P_Graph, Rest_P_Graph), + s_to_p_graph(G, Rest_P_Graph). + + +s_to_p_graph([], _, P_Graph, P_Graph) :- !. +s_to_p_graph([Neib|Neibs], Vertex, [Vertex-Neib|P], Rest_P) :- + s_to_p_graph(Neibs, Vertex, P, Rest_P). + +%! transitive_closure(+Graph, -Closure) +% +% Generate the graph Closure as the transitive closure of Graph. +% Example: +% +% ``` +% ?- transitive_closure([1-[2,3],2-[4,5],4-[6]],L). +% L = [1-[2,3,4,5,6], 2-[4,5,6], 4-[6]] +% ``` + +transitive_closure(Graph, Closure) :- + warshall(Graph, Graph, Closure). + +warshall([], Closure, Closure) :- !. +warshall([V-_|G], E, Closure) :- + memberchk(V-Y, E), % Y := E(v) + warshall(E, V, Y, NewE), + warshall(G, NewE, Closure). + + +warshall([X-Neibs|G], V, Y, [X-NewNeibs|NewG]) :- + memberchk(V, Neibs), + !, + ord_union(Neibs, Y, NewNeibs), + warshall(G, V, Y, NewG). +warshall([X-Neibs|G], V, Y, [X-Neibs|NewG]) :- + !, + warshall(G, V, Y, NewG). +warshall([], _, _, []). + +%! transpose_ugraph(Graph, NewGraph) is det. +% +% Unify NewGraph with a new graph obtained from Graph by replacing +% all edges of the form V1-V2 by edges of the form V2-V1. The cost +% is O(|V|*log(|V|)). Notice that an undirected graph is its own +% transpose. Example: +% +% == +% ?- transpose([1-[3,5],2-[4],3-[],4-[5], +% 5-[],6-[],7-[],8-[]], NL). +% NL = [1-[],2-[],3-[1],4-[2],5-[1,4],6-[],7-[],8-[]] +% == +% +% @compat This predicate used to be known as transpose/2. +% Following SICStus 4, we reserve transpose/2 for matrix +% transposition and renamed ugraph transposition to +% transpose_ugraph/2. + +transpose_ugraph(Graph, NewGraph) :- + edges(Graph, Edges), + vertices(Graph, Vertices), + flip_edges(Edges, TransposedEdges), + vertices_edges_to_ugraph(Vertices, TransposedEdges, NewGraph). + +flip_edges([], []). +flip_edges([Key-Val|Pairs], [Val-Key|Flipped]) :- + flip_edges(Pairs, Flipped). + +%! compose(+LeftGraph, +RightGraph, -NewGraph) +% +% Compose NewGraph by connecting the _drains_ of LeftGraph to the +% _sources_ of RightGraph. Example: +% +% ?- compose([1-[2],2-[3]],[2-[4],3-[1,2,4]],L). +% L = [1-[4], 2-[1,2,4], 3-[]] + +compose(G1, G2, Composition) :- + vertices(G1, V1), + vertices(G2, V2), + ord_union(V1, V2, V), + compose(V, G1, G2, Composition). + +compose([], _, _, []) :- !. +compose([Vertex|Vertices], [Vertex-Neibs|G1], G2, + [Vertex-Comp|Composition]) :- + !, + compose1(Neibs, G2, [], Comp), + compose(Vertices, G1, G2, Composition). +compose([Vertex|Vertices], G1, G2, [Vertex-[]|Composition]) :- + compose(Vertices, G1, G2, Composition). + + +compose1([V1|Vs1], [V2-N2|G2], SoFar, Comp) :- + compare(Rel, V1, V2), + !, + compose1(Rel, V1, Vs1, V2, N2, G2, SoFar, Comp). +compose1(_, _, Comp, Comp). + + +compose1(<, _, Vs1, V2, N2, G2, SoFar, Comp) :- + !, + compose1(Vs1, [V2-N2|G2], SoFar, Comp). +compose1(>, V1, Vs1, _, _, G2, SoFar, Comp) :- + !, + compose1([V1|Vs1], G2, SoFar, Comp). +compose1(=, V1, Vs1, V1, N2, G2, SoFar, Comp) :- + ord_union(N2, SoFar, Next), + compose1(Vs1, G2, Next, Comp). + +%! top_sort(+Graph, -Sorted) is semidet. +%! top_sort(+Graph, -Sorted, ?Tail) is semidet. +% +% Sorted is a topological sorted list of nodes in Graph. A +% toplogical sort is possible if the graph is connected and +% acyclic. In the example we show how topological sorting works +% for a linear graph: +% +% == +% ?- top_sort([1-[2], 2-[3], 3-[]], L). +% L = [1, 2, 3] +% == +% +% The predicate top_sort/3 is a difference list version of +% top_sort/2. + +top_sort(Graph, Sorted) :- + vertices_and_zeros(Graph, Vertices, Counts0), + count_edges(Graph, Vertices, Counts0, Counts1), + select_zeros(Counts1, Vertices, Zeros), + top_sort(Zeros, Sorted, Graph, Vertices, Counts1). + +top_sort(Graph, Sorted0, Sorted) :- + vertices_and_zeros(Graph, Vertices, Counts0), + count_edges(Graph, Vertices, Counts0, Counts1), + select_zeros(Counts1, Vertices, Zeros), + top_sort(Zeros, Sorted, Sorted0, Graph, Vertices, Counts1). + + +vertices_and_zeros([], [], []) :- !. +vertices_and_zeros([Vertex-_|Graph], [Vertex|Vertices], [0|Zeros]) :- + vertices_and_zeros(Graph, Vertices, Zeros). + + +count_edges([], _, Counts, Counts) :- !. +count_edges([_-Neibs|Graph], Vertices, Counts0, Counts2) :- + incr_list(Neibs, Vertices, Counts0, Counts1), + count_edges(Graph, Vertices, Counts1, Counts2). + + +incr_list([], _, Counts, Counts) :- !. +incr_list([V1|Neibs], [V2|Vertices], [M|Counts0], [N|Counts1]) :- + V1 == V2, + !, + N is M+1, + incr_list(Neibs, Vertices, Counts0, Counts1). +incr_list(Neibs, [_|Vertices], [N|Counts0], [N|Counts1]) :- + incr_list(Neibs, Vertices, Counts0, Counts1). + + +select_zeros([], [], []) :- !. +select_zeros([0|Counts], [Vertex|Vertices], [Vertex|Zeros]) :- + !, + select_zeros(Counts, Vertices, Zeros). +select_zeros([_|Counts], [_|Vertices], Zeros) :- + select_zeros(Counts, Vertices, Zeros). + + + +top_sort([], [], Graph, _, Counts) :- + !, + vertices_and_zeros(Graph, _, Counts). +top_sort([Zero|Zeros], [Zero|Sorted], Graph, Vertices, Counts1) :- + graph_memberchk(Zero-Neibs, Graph), + decr_list(Neibs, Vertices, Counts1, Counts2, Zeros, NewZeros), + top_sort(NewZeros, Sorted, Graph, Vertices, Counts2). + +top_sort([], Sorted0, Sorted0, Graph, _, Counts) :- + !, + vertices_and_zeros(Graph, _, Counts). +top_sort([Zero|Zeros], [Zero|Sorted], Sorted0, Graph, Vertices, Counts1) :- + graph_memberchk(Zero-Neibs, Graph), + decr_list(Neibs, Vertices, Counts1, Counts2, Zeros, NewZeros), + top_sort(NewZeros, Sorted, Sorted0, Graph, Vertices, Counts2). + +graph_memberchk(Element1-Edges, [Element2-Edges2|_]) :- + Element1 == Element2, + !, + Edges = Edges2. +graph_memberchk(Element, [_|Rest]) :- + graph_memberchk(Element, Rest). + + +decr_list([], _, Counts, Counts, Zeros, Zeros) :- !. +decr_list([V1|Neibs], [V2|Vertices], [1|Counts1], [0|Counts2], Zi, Zo) :- + V1 == V2, + !, + decr_list(Neibs, Vertices, Counts1, Counts2, [V2|Zi], Zo). +decr_list([V1|Neibs], [V2|Vertices], [N|Counts1], [M|Counts2], Zi, Zo) :- + V1 == V2, + !, + M is N-1, + decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo). +decr_list(Neibs, [_|Vertices], [N|Counts1], [N|Counts2], Zi, Zo) :- + decr_list(Neibs, Vertices, Counts1, Counts2, Zi, Zo). + + +%! neighbors(+Vertex, +Graph, -Neigbours) is det. +%! neighbours(+Vertex, +Graph, -Neigbours) is det. +% +% Neigbours is a sorted list of the neighbours of Vertex in Graph. +% Example: +% +% ``` +% ?- neighbours(4,[1-[3,5],2-[4],3-[], +% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL). +% NL = [1,2,7,5] +% ``` + +neighbors(Vertex, Graph, Neig) :- + neighbours(Vertex, Graph, Neig). + +neighbours(V,[V0-Neig|_],Neig) :- + V == V0, + !. +neighbours(V,[_|G],Neig) :- + neighbours(V,G,Neig). + + +%! connect_ugraph(+UGraphIn, -Start, -UGraphOut) is det. +% +% Adds Start as an additional vertex that is connected to all vertices +% in UGraphIn. This can be used to create an topological sort for a +% not connected graph. Start is before any vertex in UGraphIn in the +% standard order of terms. No vertex in UGraphIn can be a variable. +% +% Can be used to order a not-connected graph as follows: +% +% ``` +% top_sort_unconnected(Graph, Vertices) :- +% ( top_sort(Graph, Vertices) +% -> true +% ; connect_ugraph(Graph, Start, Connected), +% top_sort(Connected, Ordered0), +% Ordered0 = [Start|Vertices] +% ). +% ``` + +connect_ugraph([], 0, []) :- !. +connect_ugraph(Graph, Start, [Start-Vertices|Graph]) :- + vertices(Graph, Vertices), + Vertices = [First|_], + before(First, Start). + +%! before(+Term, -Before) is det. +% +% Unify Before to a term that comes before Term in the standard +% order of terms. +% +% @error instantiation_error if Term is unbound. + +before(X, _) :- + var(X), + !, + instantiation_error(X). +before(Number, Start) :- + number(Number), + !, + Start is Number - 1. +before(_, 0). + + +%! complement(+UGraphIn, -UGraphOut) +% +% UGraphOut is a ugraph with an edge between all vertices that are +% _not_ connected in UGraphIn and all edges from UGraphIn removed. +% Example: +% +% ``` +% ?- complement([1-[3,5],2-[4],3-[], +% 4-[1,2,7,5],5-[],6-[],7-[],8-[]], NL). +% NL = [1-[2,4,6,7,8],2-[1,3,5,6,7,8],3-[1,2,4,5,6,7,8], +% 4-[3,5,6,8],5-[1,2,3,4,6,7,8],6-[1,2,3,4,5,7,8], +% 7-[1,2,3,4,5,6,8],8-[1,2,3,4,5,6,7]] +% ``` +% +% @tbd Simple two-step algorithm. You could be smarter, I suppose. + +complement(G, NG) :- + vertices(G,Vs), + complement(G,Vs,NG). + +complement([], _, []). +complement([V-Ns|G], Vs, [V-INs|NG]) :- + ord_add_element(Ns,V,Ns1), + ord_subtract(Vs,Ns1,INs), + complement(G, Vs, NG). + +%! reachable(+Vertex, +UGraph, -Vertices) +% +% True when Vertices is an ordered set of vertices reachable in +% UGraph, including Vertex. Example: +% +% ?- reachable(1,[1-[3,5],2-[4],3-[],4-[5],5-[]],V). +% V = [1, 3, 5] + +reachable(N, G, Rs) :- + reachable([N], G, [N], Rs). + +reachable([], _, Rs, Rs). +reachable([N|Ns], G, Rs0, RsF) :- + neighbours(N, G, Nei), + ord_union(Rs0, Nei, Rs1, D), + append(Ns, D, Nsi), + reachable(Nsi, G, Rs1, RsF).