问题
There is an undirected graph (V,E), weights on the edges w : E → N, a target k ∈ N, and a threshold O ∈ N. Find a k-vertices tree of the graph of weight less than the threshold. In other words, select k vertices and k - 1 edges from V and E respectively such that they constitute a tree, and the sum of the weights of the selected edges are less than O.
Write an ASP program that takes V , E, w, k, and O as input, and finds a selection of edges satisfying the constraints, or outputs ‘unsatisfiable’ if the constraints cannot be satisfied. Selecting the edges implicitly induces a selection of the vertices, so there is no need for the selected vertices to be explicitly displayed.
An instance to this problem is provided through predicates vertex/1, weight/3, target/1, and threshold/1. All edges have weights, so statements of the form weight(a, b, 10). can be used to declare the existence of an edge between vertices a and b at the same time as declaring their weight, and there is no need for any redundant edge/2 predicate.
I tried the following:
% instance
vertex ( v1 ). vertex ( v2 ). vertex ( v3 ).
vertex ( v4 ). vertex ( v5 ). vertex ( v6 ).
vertex ( v7 ). vertex ( v8 ). vertex ( v9 ).
weight ( v1 , v2 ,3). weight ( v1 , v3 ,3).
weight ( v2 , v4 ,1). weight ( v2 , v5 ,5).
weight ( v3 , v4 ,3). weight ( v3 , v6 ,4).
weight ( v4 , v5 ,4). weight ( v4 , v7 ,1).
weight ( v5 , v7 ,7).
weight ( v6 , v7 ,2). weight ( v6 , v8 ,2).
weight ( v7 , v9 ,3).
weight ( v8 , v9 ,2).
target (4).
threshold (4).
% encoding
(P-1) {select(X, Y) : weight(X, Y, Z)} (Q-1) :- target(P), target(Q).
sum(S) :- S = #sum {W,X,Y : select(X,Y), weight(X,Y,W); W,X,Z : select(X,Z), weight(X,Z,W) }.
:- sum(S),threshold(M), S > M.
:- select(A,B), select(C,D), A == C ; A == D ; B == C ; B == D.
#show select/2.
And I get the following output:
clingo version 5.5.0
Reading from stdin
Solving...
Answer: 1
select(v2,v4) select(v4,v7) select(v6,v7)
Answer: 2
select(v2,v4) select(v4,v7) select(v6,v8)
Answer: 3
select(v2,v4) select(v4,v7) select(v8,v9)
SATISFIABLE
Models : 3
Calls : 1
Time : 0.013s (Solving: 0.00s 1st Model: 0.00s Unsat: 0.00s)
CPU Time : 0.000s
I was expecting just
select(v2,v4) select(v4,v7) select(v6,v7)
because the others clearly are not tress.
I think this is because of the problematic line:
:- select(A,B), select(C,D), A == C ; A == D ; B == C ; B == D.
How do I correct this?
回答1:
Ok, that was rather complicated. I'm pretty sure my solution is not perfect, I'm a beginner too.
Before we start with the code, let's check up the question once more: The requirement is to select k
vertices and k-1
edges. If you think about it a bit this can form exactly two pattern: one connected tree or multiple non-connected graphs where there is at least one cycle. So if you make sure not to have a cycle you get one connected tree.
I added some vertices to the facts to check if a tree was formed or if the cheap unconnected cycle was found, and for doing so I had to change target
and threshold
to higher values.
1
#const n = 5.
vertex ( v1; v2; v3; v4; v5; v6; v7; v8; v9 ).
vertex ( m1; m2; m3 ).
weight ( v1 , v2 ,3). weight ( v1 , v3 ,3).
weight ( v2 , v4 ,1). weight ( v2 , v5 ,5).
weight ( v3 , v4 ,3). weight ( v3 , v6 ,4).
weight ( v4 , v5 ,4). weight ( v4 , v7 ,1).
weight ( v5 , v7 ,7).
weight ( v6 , v7 ,2). weight ( v6 , v8 ,2).
weight ( v7 , v9 ,3).
weight ( v8 , v9 ,2).
weight ( m1 , m2 ,0).
weight ( m2 , m3 ,0).
weight ( m3 , m1 ,0).
target (n).
threshold (6).
And now comes the code, followed by an explanation.
% select subset of nodes and vertices
(P) {select(X) : vertex(X)} (P) :- target(P).
(P-1) {select(X, Y) : weight(X, Y, Z)} (Q-1) :- target(P), target(Q).
% postion does not matter in an undirected graph.
directed(A,B):-select(A,B).
directed(B,A):-select(A,B).
% for every selected edge all nodes are selected
:- directed(A,_), vertex(A), not select(A).
% for every selected node there exists at least one edge
:- select(A), {directed(A,B):vertex(B)}0.
% select a direction for each selected edge
{dir(A,B);dir(B,A)}==1 :- select(A,B).
% force them in an order
{ found(X,1..n) } == 1 :- select(X).
{ found(X,N):select(X) } == 1 :- N = 1..n.
% reject if one edge does not follow the order
:- found(X,NX), found(Y,NY), dir(X,Y), NY<NX.
% reject if 2 different edges end in the same vertex
:- dir(X,Z), dir(Y,Z), X!=Y.
sum(S) :- S = #sum {W,X,Y : select(X,Y), weight(X,Y,W); W,X,Z : select(X,Z), weight(X,Z,W) }.
:- sum(S),threshold(M), S > M.
#show select/2.
Explanation:
- To make it easier for me I added the selected vertices in the
select/1
predicate. - Since dealing with undirected graphs always has to check both postions I added the
directed/2
predicate which is a directed graph version of the selected edges. - Next I made sure every selected vertex has a selected edge and vice versa.
- Now comes the complicated part: to detect cycles. For this I forced every selected edge in one of its two directions by using the predicate
dir/2
. Testing for a tree is easier in a directed graph. - Next I put an order
found/2
to the vertices. The directed edgesdir/2
where only allowed to go with this order. This forces cycles to a certain behavior. - Now comes the cycle destroyer: if the selected graph has a cycle then two edges from
dir/2
will end in the same vertex. REJECT. If this was just an unlucky guess from clingo then it will find a luckier guess which fullfills this criterion. - Computation of the sum was copy and paste from you.
The output is 16 times
select(v2,v4) select(v4,v7) select(v6,v7) select(v6,v8)
The dublicates come from the fact that the order of the vertices in found/2
can differ but still get the same result.
来源:https://stackoverflow.com/questions/64765161/how-do-i-extract-the-trees-from-a-graph-using-answer-set-programming