Related
All of these predicates are defined in pretty much the same way. The base case is defined for the empty list. For non-empty lists we unify in the head of the clause when a certain predicate holds, but do not unify if that predicate does not hold. These predicates look too similar for me to think it is a coincidence. Is there a name for this, or a defined abstraction?
intersect([],_,[]).
intersect(_,[],[]).
intersect([X|Xs],Ys,[X|Acc]) :-
member(X,Ys),
intersect(Xs,Ys,Acc).
intersect([X|Xs],Ys,Acc) :-
\+ member(X,Ys),
intersect(Xs,Ys,Acc).
without_duplicates([],[]).
without_duplicates([X|Xs],[X|Acc]) :-
\+ member(X,Acc),
without_duplicates(Xs,Acc).
without_duplicates([X|Xs],Acc) :-
member(X,Acc),
without_duplicates(Xs,Acc).
difference([],_,[]).
difference([X|Xs],Ys,[X|Acc]) :-
\+ member(X,Ys),
difference(Xs,Ys,Acc).
difference([X|Xs],Ys,Acc) :-
member(X,Ys),
difference(Xs,Ys,Acc).
delete(_,[],[]).
delete(E,[X|Xs],[X|Ans]) :-
E \= X,
delete(E,Xs,Ans).
delete(E,[X|Xs],Ans) :-
E = X,
delete(E,Xs,Ans).
There is an abstraction for "keep elements in list for which condition holds".
The names are inclide, exclude. There is a library for those in SWI-Prolog that you can use or copy. Your predicates intersect/3, difference/3, and delete/3 would look like this:
:- use_module(library(apply)).
intersect(L1, L2, L) :-
include(member_in(L1), L2, L).
difference(L1, L2, L) :-
exclude(member_in(L2), L1, L).
member_in(List, Member) :-
memberchk(Member, List).
delete(E, L1, L) :-
exclude(=(E), L1, L).
But please take a look at the implementation of include/3 and exclude/3, here:
https://www.swi-prolog.org/pldoc/doc/_SWI_/library/apply.pl?show=src#include/3
Also in SWI-Prolog, in another library, there are versions of those predicates called intersection/3, subtract/3, delete/3:
https://www.swi-prolog.org/pldoc/doc/_SWI_/library/lists.pl?show=src#intersection/3
https://www.swi-prolog.org/pldoc/doc/_SWI_/library/lists.pl?show=src#subtract/3
https://www.swi-prolog.org/pldoc/doc_for?object=delete/3
Those are similar in spirit to your solutions.
Your next predicate, without_duplicates, cannot be re-written like that with include/3 or exclude/3. Your implementation doesn't work, either. Try even something easy, like:
?- without_duplicates([a,b], L).
What happens?
But yeah, it is not the same as the others. To implement it correctly, depending on whether you need the original order or not.
If you don't need to keep the initial order, you can simply sort; this removes duplicates. Like this:
?- sort(List_with_duplicates, No_duplicates).
If you want to keep the original order, you need to pass the accumulated list to the recursive call.
without_duplicates([], []).
without_duplicates([H|T], [H|Result]) :-
without_duplicates_1(T, [H], Result).
without_duplicates_1([], _, []).
without_duplicates_1([H|T], Seen0, Result) :-
( memberchk(H, Seen0)
-> Seen = Seen0 , Result = Result0
; Seen = [H|Seen0], Result = [H|Result0]
),
without_duplicates_1(T, Seen, Result0).
You could get rid of one argument if you use a DCG:
without_duplicates([], []).
without_duplicates([H|T], [H|No_duplicates]) :-
phrase(no_dups(T, [H]), No_duplicates).
no_dups([], _) --> [].
no_dups([H|T], Seen) -->
{ memberchk(H, Seen) },
!,
no_dups(T, Seen).
no_dups([H|T], Seen) -->
[H],
no_dups(T, [H|Seen]).
Well, these are the "while loops" of Prolog on the one hand, and the inductive definitions of mathematical logic on the other hand (See also: Logic Programming, Functional Programming, and Inductive Definitions, Lawrence C. Paulson, Andrew W. Smith, 2001), so it's not surprising to find them multiple times in a program - syntactically similar, with slight deviations.
In this case, you just have a binary decision - whether something is the case or not - and you "branch" (or rather, decide to not fail the body and press on with the selected clause) on that. The "guard" (the test which supplements the head unification), in this case member(X,Ys) or \+ member(X,Ys) is a binary decision (it also is exhaustive, i.e. covers the whole space of possible X)
intersect([X|Xs],Ys,[X|Acc]) :- % if the head could unify with the goal
member(X,Ys), % then additionally check that ("guard")
(...action...). % and then do something
intersect([X|Xs],Ys,Acc) :- % if the head could unify with the goal
\+ member(X,Ys), % then additionally check that ("guard")
(...action...). % and then do something
Other applications may need the equivalent of a multiple-decision switch statement here, and so N>2 clauses may have to be written instead of 2.
foo(X) :-
member(X,Set1),
(...action...).
foo(X) :-
member(X,Set2),
(...action...).
foo(X) :-
member(X,Set3),
(...action...).
% inefficient pseudocode for the case where Set1, Set2, Set3
% do not cover the whole range of X. Such a predicate may or
% may not be necessary; the default behaviour would be "failure"
% of foo/1 if this clause does not exist:
foo(X) :-
\+ (member(X,Set1);member(X,Set2);member(X,Set3)),
(...action...).
Note:
Use memberchk/2 (which fails or succeeds-once) instead of member/2 (which fails or succeeds-and-then-tries-to-succeed-again-for-the-rest-of-the-set) to make the program deterministic in its decision whether member(X,L).
Similarly, "cut" after the clause guard to tell Prolog that if a guard of one clause succeeds, there is no point in trying the other clauses because they will all turn out false: member(X,Ys),!,...
Finally, use term comparison == and \== instead of unification = or unification failure \= for delete/3.
I am putting together a simple meta interpreter which outputs the steps of a proof. I am having trouble with getting the proof steps as an output argument. My predicate explain1 returns the proof in the detailed form that i would like, but not as an output argument. My predicate explain2 returns the proof as an output argument but not with the level of detail that i would like. Can explain2 be modified so that it yields as much info as explain1? I don't need it to output text "Explaining..." and "Explanation...", just the actual explanans and explanandum.
The toy data at the bottom of the program ("if healthy and rich, then happy") is just an example and the idea is to have a database with more facts about other things. I want to try to make a predicate that accepts an effect, e.g. happy(john), and returns an explanation for it. So the E argument of explain is supposed to be entered by the user; another query might thus be explain(_, smokes(mary), _) and so on. I can't get what i want directly from the C and E variables in explain, because i want the program to output steps in the Proof process, where C and E vary, e.g. "rich and healthy, so happy; wins so rich; TRUE so rich; TRUE so happy" and so on. I.e. return all causal links that lead up to an effect.
The excellent site by Markus Triska has some details on this, but i am having trouble adapting that code to my problem.
Any help would be greatly appreciated!
Thanks/JCR
My program:
main1:-explain1(_, happy(john), _), fail.
main2:-explain2(_, happy(john), _, T), writeln(T), fail.
explain1(C, E, P):-
C = ['True'],
p(C, E, P),
write('Explaining '), write(E),
write('. An explanation is: '), write(C),
write(' with probability '), write(P), nl.
explain1(C, E, P):-
p(C, E, P),
not(C = ['True']),
write('Explaining '), write(E),
write('. An explanation is: '), write(C),
write(' with probability '), write(P), nl.
explain1(C, E, P):-
p(C0, E, P0),
maplist(explain1, C1, C0, P1),
flatten(C1, C),
append([P0], P1, P2),
flatten(P2, P3),
foldl(multiply, P3, 1, P),
write('Explaining '), write(E),
write('. An explanation is: '), write(C),
write(' with probability '), write(P), nl.
explain2(C, E, P, T):-
C = ['True'],
p(C, E, P),
T = [C, E, P].
explain2(C, E, P, T):-
p(C, E, P),
not(C = ['True']),
T = [C, E, P].
explain2(C, E, P, T):-
p(C0, E, P0),
maplist(explain2, C1, C0, P1, _),
flatten(C1, C),
append([P0], P1, P2),
flatten(P2, P3),
foldl(multiply, P3, 1, P),
T = [C, E, P].
multiply(V1, V2, R) :- R is V1 * V2.
p(['True'], wins(john), 0.7).
p([wins(john)], rich(john), 0.3).
p(['True'], healthy(john), 0.9).
p([rich(john), healthy(john)], happy(john), 0.6).
The output of main1:
Explaining happy(john). An explanation is: [rich(john), healthy(john)] with probability 0.6
Explaining rich(john). An explanation is: [wins(john)] with probability 0.3
Explaining healthy(john). An explanation is: [True] with probability 0.9
Explaining happy(john). An explanation is: [wins(john), True] with probability 0.162
Explaining wins(john). An explanation is: [True] with probability 0.7
Explaining rich(john). An explanation is: [True] with probability 0.21
Explaining healthy(john). An explanation is: [True] with probability 0.9
Explaining happy(john). An explanation is: [True, True] with probability 0.1134
The output of main2:
[[rich(john), healthy(john)], happy(john), 0.6]
[[wins(john), True], happy(john), 0.162]
[[True, True], happy(john), 0.1134]
I'm unclear on the probability portion of this metainterpreter, but I actually think it's incidental to your question so I'm going to try and sketch out how I would approach this.
You can think of call/1 as the prototypical interpreter for Prolog, because it simply proves a single goal. So it seems like the API you want is something like prove(+Goal, -Proof), where Goal gets proven just like it does with call/1, but you get a second thing back, a proof of some kind.
When normal Prolog sees an expression like Goal1, Goal2, you could think of it expanding into call(Goal1), call(Goal2). So what does your proof-returning metainterpreter do in this situation instead? It should prove both goals and then somehow combine those "subproofs".
All this suggests to me that something missing from your conception is, what is the structure of a proof? I would think hard about what kind of thing you're going to get back, because if you don't want a string, you'll want something you can traverse more easily. It will probably wind up having a tree structure similar to what Prolog does (except without the failure branches). I would thus expect it to have some kind of nesting and it could certainly "resemble" the call stack somehow, although I expect this would limit its utility for you (how are you going to traverse that tree usefully for a generic query?).
Let's consider your base case. It's probably something like this:
prove(true, true) :- !.
True is intrinsically true, because it is true.
The next case I would be interested in is "and".
prove((G1, G2), (P1, P2)) :-
!,
prove(G1, P1),
prove(G2, P2).
This looks fairly tautological, but the key idea really is that we are combining the proofs of G1 and G2 with (P1, P2) in the proof.
The next case would be "or" probably:
prove((G1;_), P1) :- prove(G1, P1).
prove((_;G2), P2) :- !, prove(G2, P2).
This is the part where we are losing the failing branches. If the first branch succeeds, its proof will appear in the result; if the second branch succeeds instead, its proof will appear in the result. But they won't ever both appear in the result.
Finally we must handle builtins and user predicates, per a question I asked some time ago:
prove(H, subproof(H, Subproof)) :- clause(H, Body), prove(Body, Subproof).
prove(H, builtin(H)) :- call(H).
At this point we have a metainterpreter that produces very simple proofs. I'm going to add a few clauses and then try it with our metainterpreter:
mortal(X) :- man(X).
man(socrates).
Here's the query:
?- prove((member(X, [bread,socrates]), mortal(X)), Proof).
X = socrates,
Proof = (builtin(member(socrates, [bread, socrates])),
subproof(mortal(socrates),
subproof(man(socrates), true)))
For reasons I do not yet understand, the use of member/2 will bomb out on a second query. I have opened a question about that on the SWI forum and will update this answer when I find out what's going on there.
Update. The issue is related to the autoloading of library(lists) which happens when you use member/2. On the first call, member/2 has no clauses, so it enters call/1, which invokes the autoloader and then invokes it as a built-in. On a subsequent attempt, member/2 has clauses, but their bodies involve predicates in the lists module, and this metainterpreter does not handle modules properly. A quick-and-dirty solution is to change the third clause to this:
prove(H, subproof(H, Subproof)) :-
\+ predicate_property(H, imported_from(_)),
clause(H, Body),
prove(Body, Subproof).
I hope this helps!
I wonder, is it possible to do planning in Prolog using the knowledge base modified by retract and assert during the runtime?
My idea is as follows: assume that I need to replace a flat tire of a car. I can either put something on the ground or move something from the ground to some free place.
So I came up with such a code:
at(flat, axle).
at(spare, trunk).
free(Where) :- at(_, Where), !, fail.
remove(What) :- at(What, _), retract(at(What, _)), assert(at(What, ground)).
put_on(What, Where) :- at(What, _), free(Where), retract(at(What, _)), assert(at(What, Where)).
(I am a rookie in Prolog so maybe that it is even wrong, if so, please advise me how to correct it.)
The idea is: I have a flat tire on the axle and a spare one in the trunk. I can remove a thing X if X is somewhere and to remove it, I remove the fact specifying where it is and add a fact that it is on the ground. Similarly, I can put a thing X to location Y if X is somewhere and Y is free and to do so, I remove X from where it is and add the fact that X is at Y.
And now I am stuck: I have no idea how to use this code now, since at(spare, axle) just says nope, even with tracing.
So the question: can such an approach be used and if so, how?
I hope it makes sense.
Using the example code from "Artificial Intelligence - Structures and Strategies for Complex Problem Solving" by George F Luger (WorldCat)
adts
%%%
%%% This is one of the example programs from the textbook:
%%%
%%% Artificial Intelligence:
%%% Structures and strategies for complex problem solving
%%%
%%% by George F. Luger and William A. Stubblefield
%%%
%%% Corrections by Christopher E. Davis (chris2d#cs.unm.edu)
%%%
%%% These programs are copyrighted by Benjamin/Cummings Publishers.
%%%
%%% We offer them for use, free of charge, for educational purposes only.
%%%
%%% Disclaimer: These programs are provided with no warranty whatsoever as to
%%% their correctness, reliability, or any other property. We have written
%%% them for specific educational purposes, and have made no effort
%%% to produce commercial quality computer programs. Please do not expect
%%% more of them then we have intended.
%%%
%%% This code has been tested with SWI-Prolog (Multi-threaded, Version 5.2.13)
%%% and appears to function as intended.
%%%%%%%%%%%%%%%%%%%% stack operations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% These predicates give a simple, list based implementation of stacks
% empty stack generates/tests an empty stack
member(X,[X|_]).
member(X,[_|T]):-member(X,T).
empty_stack([]).
% member_stack tests if an element is a member of a stack
member_stack(E, S) :- member(E, S).
% stack performs the push, pop and peek operations
% to push an element onto the stack
% ?- stack(a, [b,c,d], S).
% S = [a,b,c,d]
% To pop an element from the stack
% ?- stack(Top, Rest, [a,b,c]).
% Top = a, Rest = [b,c]
% To peek at the top element on the stack
% ?- stack(Top, _, [a,b,c]).
% Top = a
stack(E, S, [E|S]).
%%%%%%%%%%%%%%%%%%%% queue operations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% These predicates give a simple, list based implementation of
% FIFO queues
% empty queue generates/tests an empty queue
empty_queue([]).
% member_queue tests if an element is a member of a queue
member_queue(E, S) :- member(E, S).
% add_to_queue adds a new element to the back of the queue
add_to_queue(E, [], [E]).
add_to_queue(E, [H|T], [H|Tnew]) :- add_to_queue(E, T, Tnew).
% remove_from_queue removes the next element from the queue
% Note that it can also be used to examine that element
% without removing it
remove_from_queue(E, [E|T], T).
append_queue(First, Second, Concatenation) :-
append(First, Second, Concatenation).
%%%%%%%%%%%%%%%%%%%% set operations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% These predicates give a simple,
% list based implementation of sets
% empty_set tests/generates an empty set.
empty_set([]).
member_set(E, S) :- member(E, S).
% add_to_set adds a new member to a set, allowing each element
% to appear only once
add_to_set(X, S, S) :- member(X, S), !.
add_to_set(X, S, [X|S]).
remove_from_set(_, [], []).
remove_from_set(E, [E|T], T) :- !.
remove_from_set(E, [H|T], [H|T_new]) :-
remove_from_set(E, T, T_new), !.
union([], S, S).
union([H|T], S, S_new) :-
union(T, S, S2),
add_to_set(H, S2, S_new).
intersection([], _, []).
intersection([H|T], S, [H|S_new]) :-
member_set(H, S),
intersection(T, S, S_new),!.
intersection([_|T], S, S_new) :-
intersection(T, S, S_new),!.
set_diff([], _, []).
set_diff([H|T], S, T_new) :-
member_set(H, S),
set_diff(T, S, T_new),!.
set_diff([H|T], S, [H|T_new]) :-
set_diff(T, S, T_new), !.
subset([], _).
subset([H|T], S) :-
member_set(H, S),
subset(T, S).
equal_set(S1, S2) :-
subset(S1, S2), subset(S2, S1).
%%%%%%%%%%%%%%%%%%%%%%% priority queue operations %%%%%%%%%%%%%%%%%%%
% These predicates provide a simple list based implementation
% of a priority queue.
% They assume a definition of precedes for the objects being handled
empty_sort_queue([]).
member_sort_queue(E, S) :- member(E, S).
insert_sort_queue(State, [], [State]).
insert_sort_queue(State, [H | T], [State, H | T]) :-
precedes(State, H).
insert_sort_queue(State, [H|T], [H | T_new]) :-
insert_sort_queue(State, T, T_new).
remove_sort_queue(First, [First|Rest], Rest).
planner
%%%%%%%%% Simple Prolog Planner %%%%%%%%
%%%
%%% This is one of the example programs from the textbook:
%%%
%%% Artificial Intelligence:
%%% Structures and strategies for complex problem solving
%%%
%%% by George F. Luger and William A. Stubblefield
%%%
%%% Corrections by Christopher E. Davis (chris2d#cs.unm.edu)
%%%
%%% These programs are copyrighted by Benjamin/Cummings Publishers.
%%%
%%% We offer them for use, free of charge, for educational purposes only.
%%%
%%% Disclaimer: These programs are provided with no warranty whatsoever as to
%%% their correctness, reliability, or any other property. We have written
%%% them for specific educational purposes, and have made no effort
%%% to produce commercial quality computer programs. Please do not expect
%%% more of them then we have intended.
%%%
%%% This code has been tested with SWI-Prolog (Multi-threaded, Version 5.2.13)
%%% and appears to function as intended.
:- [adts].
plan(State, Goal, _, Moves) :- equal_set(State, Goal),
write('moves are'), nl,
reverse_print_stack(Moves).
plan(State, Goal, Been_list, Moves) :-
move(Name, Preconditions, Actions),
conditions_met(Preconditions, State),
change_state(State, Actions, Child_state),
not(member_state(Child_state, Been_list)),
stack(Child_state, Been_list, New_been_list),
stack(Name, Moves, New_moves),
plan(Child_state, Goal, New_been_list, New_moves),!.
change_state(S, [], S).
change_state(S, [add(P)|T], S_new) :- change_state(S, T, S2),
add_to_set(P, S2, S_new), !.
change_state(S, [del(P)|T], S_new) :- change_state(S, T, S2),
remove_from_set(P, S2, S_new), !.
conditions_met(P, S) :- subset(P, S).
member_state(S, [H|_]) :- equal_set(S, H).
member_state(S, [_|T]) :- member_state(S, T).
reverse_print_stack(S) :- empty_stack(S).
reverse_print_stack(S) :- stack(E, Rest, S),
reverse_print_stack(Rest),
write(E), nl.
/* sample moves */
move(pickup(X), [handempty, clear(X), on(X, Y)],
[del(handempty), del(clear(X)), del(on(X, Y)),
add(clear(Y)), add(holding(X))]).
move(pickup(X), [handempty, clear(X), ontable(X)],
[del(handempty), del(clear(X)), del(ontable(X)),
add(holding(X))]).
move(putdown(X), [holding(X)],
[del(holding(X)), add(ontable(X)), add(clear(X)),
add(handempty)]).
move(stack(X, Y), [holding(X), clear(Y)],
[del(holding(X)), del(clear(Y)), add(handempty), add(on(X, Y)),
add(clear(X))]).
go(S, G) :- plan(S, G, [S], []).
test :- go([handempty, ontable(b), ontable(c), on(a, b), clear(c), clear(a)],
[handempty, ontable(c), on(a,b), on(b, c), clear(a)]).
Most of the code stays the same, the only changes needed to solve your question are the predicates move/3 and the query test. Either comment out or remove the predicates move/3 and test/0 from the above code before adding predicates to solve your question.
Below is all of the new predicates needed, move/3 and test/0. The first move/3 is shown and the remainder need to be revealed (click Reveal spoiler) so that you can see them if needed but you should try to do them yourself.
move(take_from_trunk(X), [hand(empty), trunk(X)],
[del(hand(empty)), del(trunk(X)),
add(hand(X)), add(trunk(empty))]).
The state keeps track of four locations, hand, ground, axle, and trunk, and three values, flat, spare, and empty for the locations. The predicate move/3 also makes uses of variables so that they are not fixed in what they can do.
The move/3 predicate has 3 parameters.
Name: What appears in the answer, e.g. take_from_trunk(spare).
Preconditions: The conditions that have to be present in state for the move to be applied.
Actions: The changes made to state if the move is applied. These take the place of your assert and retract. The changes are very simple, you remove some of the properties of state, e.g. del(hand(empty)) and add some, e.g. add(hand(X)). For your given problem, this solution is simple in that for each change, for every del there is a matching add.
The query:
test :- go([hand(empty), trunk(spare), axle(flat), ground(empty)],
[hand(empty), trunk(flat), axle(spare), ground(empty)]).
Example run:
?- test.
moves are
take_from_trunk(spare)
place_on_ground(spare)
take_off_axle(flat)
place_in_trunk(flat)
pickup_from_ground(spare)
place_on_axle(spare)
true.
Other move/3 predicates needed. Try to do this on your own.
move(take_off_axle(X), [hand(empty), axle(X)],
[del(hand(empty)), del(axle(X)),
add(hand(X)), add(axle(empty))]).
move(place_on_ground(X), [hand(X), ground(empty)],
[del(hand(X)), del(ground(empty)),
add(hand(empty)), add(ground(X))]).
move(pickup_from_ground(X), [hand(empty), ground(X)],
[del(hand(empty)), del(ground(X)),
add(hand(X)), add(ground(empty))]).
move(place_on_axle(X), [hand(X), axle(empty)],
[del(hand(X)), del(axle(empty)),
add(hand(empty)), add(axle(X))]).
move(place_in_trunk(X), [hand(X), trunk(empty)],
[del(hand(X)), del(trunk(empty)),
add(hand(empty)), add(trunk(X))]).
In writing these predicates some of move/3 were not working as I expected so I created simple test queries for each to check them.
Using the test also helped me to change what was in state and how it was represented, e.g, instead of handempty and holding(X) it was changed to hand(empty) and hand(X) which was easier to understand, follow, and check for consistency of the code, but most likely made the code more inefficient.
test_01 :- go([hand(empty), trunk(spare), axle(flat), ground(empty)],
[hand(spare), trunk(empty), axle(flat), ground(empty)]).
test_02 :- go([hand(empty), trunk(spare), axle(flat), ground(empty)],
[hand(flat), trunk(spare), axle(empty), ground(empty)]).
test_03 :- go([hand(flat), trunk(spare), axle(empty), ground(empty)],
[hand(empty), trunk(spare), axle(empty), ground(flat)]).
test_04 :- go([hand(empty), trunk(spare), axle(empty), ground(flat)],
[hand(flat), trunk(spare), axle(empty), ground(empty)]).
test_05 :- go([hand(spare), trunk(empty), axle(empty), ground(flat)],
[hand(empty), trunk(empty), axle(spare), ground(flat)]).
test_06 :- go([hand(flat), trunk(empty), axle(spare), ground(empty)],
[hand(empty), trunk(flat), axle(spare), ground(empty)]).
Some of these test work as expected using just one move, while others return many moves. I did not modify the move/3 here so that only one move/3 is considered, but they can be modified if you so choose. Think guard statements or constraints.
The other reason the test results are listed here is to show that some of the moves are not picked in the way you would think, or intended and don't work exactly as you would expect, but yet the query to the posted question works as expected. So if you write test cases and they return something like this, don't assume your move/3 is invalid, or has bugs, they may not. When you get all of the move/3 and the final query working as expected, then go back and try to understand why these multiple moves are happening, and then modify them if you desire.
?- test_01.
moves are
take_from_trunk(spare)
true.
?- test_02.
moves are
take_from_trunk(spare)
place_on_ground(spare)
take_off_axle(flat)
place_in_trunk(flat)
pickup_from_ground(spare)
place_on_axle(spare)
take_from_trunk(flat)
place_on_ground(flat)
take_off_axle(spare)
place_in_trunk(spare)
pickup_from_ground(flat)
true.
?- test_03.
moves are
place_on_ground(flat)
true.
?- test_04.
moves are
take_from_trunk(spare)
place_on_axle(spare)
pickup_from_ground(flat)
place_in_trunk(flat)
take_off_axle(spare)
place_on_ground(spare)
take_from_trunk(flat)
place_on_axle(flat)
pickup_from_ground(spare)
place_in_trunk(spare)
take_off_axle(flat)
true.
?- test_05.
moves are
place_on_axle(spare)
true.
?- test_06.
moves are
place_on_ground(flat)
take_off_axle(spare)
place_in_trunk(spare)
pickup_from_ground(flat)
place_on_axle(flat)
take_from_trunk(spare)
place_on_ground(spare)
take_off_axle(flat)
place_in_trunk(flat)
pickup_from_ground(spare)
place_on_axle(spare)
true.
I'm learning Prolog and I'm writing STRIPS algorithm. Exactly I try to write it because it doesn’t work and unfortunately, I can't understand why.
Simply, the program doesn't stop, it thinks what kind is next action but doesn't apply it. Below, I post a code of my program, I hope that there will be somebody with more experience could put me in right direction.
May be the recursion is wrong but I can't find a mistakes.
Thank you!
The plan/2 and action/4 are predicate written in world representation file.
strips(Plan):-
[worldblock_rap],
plan(Initstate,Goallist),
strip1(Initstate,Goallist,RevPlan,[]),
reverse(RevPlan, Plan).
%strips(+GoalList, +State, +Plan, +ForbiddenActions
strip1(State,Goallist,_,_):-
there_is(State, Goallist).
%strips(+GoalList, +State, +Plan, +ForbiddenActions
strip1(State,Goallist,Plan,ForibiddenActions):-
%*****choose the right action******
action(Ac,Prec,Del,Add),
there_is(Goal, Goallist),
\+there_is(Goal, State),
there_is(Add,Goal),
\+belongs(Ac,ForibiddenActions),
%*****************
%******achive its precondition*******
strip1(TmpState1,Prec,TmpPlan1,[Ac| ForibiddenActions]),
%********************************
%***********Update the new plan with precondition's subplan and this action
apply_rule(Ac,Del,Add,TmpState1,NewState),
append([Ac|TmpPlan1], Plan, NewPlan),
strip1(NewState,Goallist,NewPlan,[Ac|ForibiddenActions]).
apply_rule(Ac,Dellist,Addlist,State,NewState):-
nl,write("doing"), write(Ac), ttyflush,
delete_list(State, Dellist,TmpState),
append(Addlist,TmpState,NewState).
reverse([],A,A).
reverse([X|L],L1,A):-reverse(L,[X|L1],A).
delete_list([H|T], List, Final):-
remove(H, List, Tmp),
delete_list(T, Tmp, Final).
delete_list([], List, List).
remove(X, [X|T], T).
remove(X, [H|T], [H|R]):-
remove(X, T, R).
append([H|T], L1, [H|L2]):-
append(T, L1, L2).
append([], L, L).
belongs(X, [X|_]).
belongs(X, [_|T]):-
belongs(X, T).
there_is([], _).
there_is([X|T], L):-
belongs(X, L),
there_is(T, L).
This the predicate of world block representation:
plan([on(a,d),on(b,table),on(c,b),on(d,table),top(a),top(c)],
[on(a,table),on(b,a),on(c,b),on(d,c),top(d)]).
action(putdown(X),
[top(X)],
[top(X),on(X,Y)],
[on(X,table),top(Y)]).
action(pickup(X,Y),
[on(X,table),top(Y)],
[on(X,table),top(Y)],
[on(X,Y),top(X)]).
Below there is onother representation of monkey world. Also with this file the planner doesn't work.
plan([at(monkey,a),at(box,c),on(monkey,floor),on(box,floor),status(banana,notpick),at(banana,d)],
[on(monkey,box),on(box,floor),status(banana,pick),at(banana,d),at(monkey,d),at(box,d)]).
action(
go(X,Y),
[at(monkey,X),on(monkey,floor)],
[at(monkey,X)],
[at(monkey,Y)]).
action(
push(B,X,Y),
[at(monkey,X),at(B,X),on(B,floor),on(monkey,floor)],
[at(monkey,X),at(B,X)],
[at(monkey,Y),at(B,Y)]).
action(
climb_on(B),
[at(monkey,X),at(box,X),on(monkey,floor),on(box,floor)],
[on(monkey,floor)],
[on(monkey,B)]).
action(
grab(B),
[status(B,notpick),on(monkey,box),at(B,X),at(monkey,X),at(box,X)],
[status(B,notpick)],
[status(B,pick)]).
This is the code that i am trying to understand.
co(X) :- co(X,[],L).
co([],A,A):- write(A).
co([X|Xs], A, L) :- p(X-Z,A,R), !, Z1 is Z+1, co(Xs, [X-Z1|R], L).
co([X|Xs], A, L) :- co(Xs, [X-1|A], L).
p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).
What is the use of '!' and predicate p(,,) in the above code. OR Can anybody just add comments in every step of the above code so that i can able to understand . Thanks.
There are many things to address in your program. Cuts are not even the major concern. Please, bring me the broom.
Clean up the interface
What is the precise interface you are after? The purpose of co(Xs) currently, is to produce a side effect. Otherwise it can succeed or fail for a given list. But not more than that. Yet, this side effect is not at all needed - and is for most situations not a helpful approach, since such a program is practically unreusable and defies any logical reasoning. You need to leave a hole to let some result lurk out of the relation. Add another argument and remove the goal write/1 in co/3.
co(Xs, D) :-
co(Xs, [], D).
Now you can test the program with the top-level shell alone. You do not need any harness or sandbox to check for the "output". It is there, readily in a separate argument.
Clean up the program structure
Next is co/3 itself. Here, the best is to clarify the intention by separating a bit the concerns, and making these extra arguments a bit more intention-revealing. D stands for dictionary. Another good name would be KVs meaning list (the plural s) of key-value pairs. Note how the different states are numbered: They start with D0, D1, ... and at the end there is D. In this manner, if you start to write a rule, you can put D0,D already in the head without knowing how many states you will need in that rule.
co([], D,D).
co([X|Xs], D0,D) :-
nn(X, D0,D1),
co(Xs, D1,D).
nn(K, D0,D) :-
p(K-V0,D0,D1), !,
V is V0+1,
D = [X-V|D1].
nn(K, D0,D) :-
D = [K-1|D0].
p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).
co/3 now more clearly reveals its intention. It somehow relates the elements of a list to some state that is "updated" for each element. There is a word for this: This is a left-fold. And there is even a predicate for it: foldl/4. So we could equally define co/3 as:
co(Xs, D0,D) :-
foldl(nn, Xs, D0,D).
or better get rid of co/3 altogether:
co(Xs, D) :-
foldl(nn, Xs, [], D).
foldl(_C_3, [], S,S).
foldl(C_3, [X|Xs], S0,S) :-
call(C_3, X, S0,S1),
foldl(C_3, Xs, S1,S).
Note, that so far, I have not even touched any cuts of yours, these are now their last moments...
Remover superfluous cuts
The cut in p/3 does not serve any purpose. There is a cut immediately after the goal p/3 anyway. Then, X-Y is not needed in p/3, you can safely replace it by another variable. In short, p/3 is now the predicate select/3 from the Prolog prologue.
select(E, [E|Xs], Xs).
select(E, [X|Xs], [X|Ys]) :-
select(E, Xs, Ys).
nn(K, D0,D) :-
select(K-V0, D0,D1), !,
V is V0+1,
D = [K-V|D1].
nn(K, D0,D) :-
D = [K-1|D0].
This one remaining cut cannot be removed so easily: it protects the alternate clause from being used should K-V not occur in D. However, there are still better ways to express this.
Replace cuts with (\+)/1
nn(K, D0,D) :-
select(K-V0, D0,D1),
V is V0+1,
D = [K-V|D1].
nn(K, D0,D) :-
\+select(K-_, D0,_),
D = [K-1|D0].
Now, each rule states what it wants for itself. This means, that we can now freely change the order of those rules. Call it superstition, but I prefer:
nn(K, D0,D) :-
\+select(K-_, D0,_),
D = [K-1|D0].
nn(K, D0,D) :-
select(K-V0, D0,D1),
V is V0+1,
D = [K-V|D1].
Purify with dif/2
To make this into a true relation, we need to get rid of this negation. Instead of saying, that there is no solution, we can instead demand that all keys (key is the first argument in Key-Value) are different to K.
nokey(_K, []).
nokey(K, [Kx-|KVs]) :-
dif(K, Kx),
nokey(K, KVs).
nn(K, D,[K-1|D]) :-
nokey(K, D).
nn(K, D0,[K-V|D]) :-
select(K-V0, D0,D),
V is V0+1.
With the help of lambdas, nokey(K, D) becomes maplist(K+\(Kx-_)^dif(Kx,K), D)
To summarize, we have now:
co(Xs, D) :-
foldl(nn, Xs, [], D).
nn(K, D,[K-1|D]) :-
maplist(K+\(Kx-_)^dif(Kx,K), D).
nn(K, D0,[K-V|D]) :-
select(K-V0, D0,D),
V is V0+1.
So what is this relation about: The first argument is a list, and the second argument a Key-Value list, with each element and the number of occurrences in the list.
Beginners tend to use !/0 because they are not aware of its negative consequences.
This is because most Prolog textbooks that are popular among beginners are quite bad and often contain wrong and misleading information about !/0.
There is an excellent answer by #false on when to use !/0. In summary: don't.
Instead, focus on a declarative description about what holds, and try to make the description elegant and general using pure and monotonic methods like constraints, clean representations, ...