It's nice to ask a question based on homework, not just homework! Let's start with your predicate and see if we can beat it into submission, and then talk about some alternative approaches.
First, I start with a simplified predicate from yours:
path(X, Y, N, [XY]) :- edge(X, Y, N). path(X, Z, N, [XY|T]) :- edge(X, Y, N0), path(Y, Z, N1, T), N is N0 + N1.
The main difference here is that I just generate paths and then calculate lengths. I'm not doing subtraction here. It is well known that in Prolog you can start with the simplest method of generating and testing, and then refine either the generator or the test, or both, until you are happy, so this is just a simple generator. I save both the source and destination nodes in the path sequence to just help me visualize what is happening, and with it you immediately see the problem with loops:
?- path(a, e, N, T). N = 4, T = [ab, bc, ce] ; N = 18, T = [ab, bc, ce, ea, ab, bc, ce] ; N = 32, T = [ab, bc, ce, ea, ab, bc, ce, ea, ... - ...|...] .
I donโt think we are with your example graph, but we could work a little here from the first Prolog search: until there are no failures, Prolog has no reason to back up and try a different way. And you see the cycles right there. If you used breadth-first instead, you would be sure that the first solution is the shortest, simply because, advancing everything by one step, you never end up in a rabbit hole before creating your first solution. Dijkstra's algorithm (thanks for reminding @JakobLovern) circumvents the problem by coloring the visited nodes and not counting them more than once.
You can control the search behavior by creating a metainterpreter, which is not as bad as it seems, but it is more than setting up a search to account for loops, which I think most people do in this case with a graph, so try again:
path(X, Y, N, Path) :- path(X, Y, N, [], Path). path(X, Y, N, Seen, [X]) :- \+ memberchk(X, Seen), edge(X, Y, N). path(X, Z, N, Seen, [X|T]) :- \+ memberchk(X, Seen), edge(X, Y, N0), path(Y, Z, N1, [X|Seen], T), \+ memberchk(X, T), N is N0 + N1.
Adding the Seen parameter and using \+ memberchk/2 to avoid adding things to the path that are already in the path is not really unusual. memberchk/2 not an ISO, but it is a very common predicate. You could implement this yourself (don't do this!):
memberchk(X, L) :- once(member(X, L)). member(X, [X|_]). member(X, [_|Xs]) :- member(X, Xs).
I find it worth noting that memberchk/2 + lists are basically equal to the sets used in Dijkstra's algorithm. This is similar to in in Python; it would be crazy to try to do something real in Prolog, at least member/2 .
These changes make path/4 avoid the loops, so now you can find all the solutions without any false. Note I have not made your schedule acyclic. I just made path/4 loop knowledge.
Note that we get several solutions:
?- path(a, d, X, Y). X = 5, Y = [a, b] ; X = 4, Y = [a, b, c] ; X = 10, Y = [a, b, c, e] ; false.
There is a good library, aggregate , which is useful for such situations. But you did not ask for false libraries. :)
Letโs just get the shortest path:
uniq_shortest_path(X, Y, MinCost, Path) :- path(X, Y, MinCost, Path), \+ (path(X, Y, LowerCost, OtherPath), OtherPath \= Path, LowerCost =< MinCost).
This literally says that Path is the shortest path between X and Y (which has a MinCost value) if and only if there is no other path with a value less than or equal to our value. Attempt:
?- uniq_shortest_path(a, d, MinCost, Path). MinCost = 4, Path = [a, b, c] ;
This trick is not cheap; it probably works by comparing all solutions to each other. But it works, without any additional fraud.
Significant improvement is likely to be achieved by obtaining all decisions, sorting by cost and then ensuring that the first two do not have the same value before reporting the cost and path of the first.
A more significant improvement could probably be found by executing Dijkstra's algorithm directly or, perhaps, by trying to make the first meta-interpreter. Performing an iterative-deepening approach is likely to work, but I doubt it will work better since it often has to do and repeat all the work leading to a result that is priced too expensive.
Anyway, I hope this helps! Be delighted with the Prologue!