Prolog Logic Tips - prolog

Learning some basic prolog and am having difficulty wrapping my head around the logic.
Scenario: An individual contracts viral meningitis and happens to interact with various other individuals. This is my prolog logic so far.
%-- Set a sickness condition.
%-- -------------------------------------------------------- --%
setILL(X) :- write(X), write(' is ill'), nl.
%-- Link some interactions between individuals.
%-- -------------------------------------------------------- --%
interact(ella, james).
interact(ella, tyrone).
interact(james, ben).
interact(james, frank).
interact(james, carl).
interact(carl, james).
interact(carl, evan).
interact(evan, mike).
interact(evan, kelly).
interact(mike, frank).
interact(kelly, carl).
interact(kelly, frank).
interact(kelly, ben).
interact(sven, mike).
%-- Create an interaction condition.
%-- -------------------------------------------------------- --%
came_in_contact(X, Y) :- setILL(X), write(X), write(' has had contact with '), write(Y), X\=Y, !, nl.
%-- Create a rule for sickness
%-- -------------------------------------------------------- --%
sick(X) :- interact(X, Y), contact(X, Y), Y\=X.
whosick(R) :- findall([X], sick(X), R).
Now the interactions are what they are supposed to be and there should be two paths each start with ella (whom is originally sick) and ends with sven (the supposed last individual to be sick). I simply wish to print out the two possible paths without including useless interactions. E.g. tyrone will talk to no one else and neither will ben. I also wish to remove repeasts (see below).
When I execute
whosick(X).
I get
ella is ill
ella has had contact with james
ella is ill
ella has had contact with tyrone
james is ill
james has had contact with ben
james is ill
james has had contact with frank
james is ill
james has had contact with carl
carl is ill
carl has had contact with james
carl is ill
carl has had contact with evan
evan is ill
evan has had contact with mike
evan is ill
evan has had contact with kelly
mike is ill
mike has had contact with frank
kelly is ill
kelly has had contact with carl
kelly is ill
kelly has had contact with frank
kelly is ill
kelly has had contact with ben
sven is ill
sven has had contact with mike
X = [[ella], [ella], [james], [james], [james], [carl], [carl], [evan], [...]|...].

First of all, there's a typo in the code you provided: came_in_contact should be contact or it won't run. Minor issue.
Second issue: I'm not sure what you mean by this: findall([X], sick(X), R). There's no especial reason to use [X] here instead of just X, and the results look a little nicer with this change:
X = [ella, ella, james, james, james, carl, carl|...].
An issue with more significance is that, stylistically, setill is 100% side-effects despite the stateful-sounding name. setill doesn't "set" anyone "ill," it just prints to standard output that someone is ill. It is, you might say, part of the "view" if this were MVC. And so part of the problem you're having is that you're calling this "view" code from deep within the "model," in sick/2.
You mention parenthetically that Ella is the origin of the outbreak, but there's no fact in your Prolog database, so Prolog is certainly not aware of it. Additionally, you seem to be interested in the "path" that infection took, but your Prolog doesn't know anything about a path—in fact, it's really just dumping out your fact database. To prove it, let's add a new fact at the top:
interact(gail, hank).
Sure enough, it's now the first "solution" even though Gail and Hank are isolated from the rest of the graph:
gail is ill
gail has had contact with hank
… (old output repeated)
...
So, you're kind of off in the weeds here. You have an incomplete fact database, your rules don't really capture the logic of the problem and they intersperse logic with printing. By the time we're done here the code is going to look pretty different. I'm not sure whether this is homework or not, it sounds like you're self-studying, but it has kind of a homeworky vibe so I'm going to try and sketch out how I would proceed without putting it all together.
First, you need to make Prolog aware of all the facts it needs to compute the solution. To wit, you have to add a fact about the originator:
infected(ella) :- !.
This is going to become the base case. Now we need to employ inductive reasoning and say, a person is infected if that person has been in contact with an infected person:
infected(X) :- interact(X, Y), X \= Y, infected(Y), !.
Note: these cuts are fairly important. There's no need to compute another solution because a person either is or is not infected. If we succeed on either branch proving they are infected, there's nothing else to say.
Now we can get reasonable solutions for some people:
?- infected(ella).
true.
?- infected(gail).
false.
Other people seem to get no solution:
?- infected(james).
(I typed Ctrl+C)
^CAction (h for help) ? abort
% Execution Aborted
The reason James doesn't arrive at a solution is because Prolog is using a depth-first search. Handily, the next thing you have to do is discover the infection path, so if you can prevent Prolog from trying people who are already in the path, you can solve the problem by getting the path you also need. You're going to have to do employ a similar base case/inductive case structure, but pass along an additional argument for the path of the infection. You can find examples of this kind of thing all over so I won't bore you with the details here.
Do take note of this: we are not going to intermix the logic of the problem with the displaying of results. This is just good policy with Prolog because of backtracking. If you print something out because a binding succeeded here, and in the next term it fails, the whole failure could go back past the printout, leaving a confused user. We can easily trick Prolog into printing out lies from solutions that failed later on. So you always want to write your Prolog so that it finds the solutions and then displays them separately. Think model-view-controller.
So let's assume you have found a predicate, path/3 (presumably path(Source, Last, Path)). When you run it, you're going to get solutions like this:
?- path(ella, X, Path).
X = sven
Path = [ella, james, ...] ;
X = sven
Path = [ella, tyrone, ...] ;
false.
This is the predicate you'll want to wrap with your findall/3, and then you'll want to walk through the results and print out the parts you need path-by-path.
Edit: In response to your comment, let's take a look at your new predicate:
path(_, X, P) :- findall(X, interact(_, X), P).
I'm afraid this isn't any closer than before. Let's see what happens when I ask for the path from myself:
?- path('Daniel Lyons', X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
In fact you can put absolutely anything in there and you'll get exactly the same result:
?- path('Jack Donaghy', X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
?- path(3.1415926, X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
?- path([a,b,c,d,e], X, Path).
Path = [james, tyrone, ben, frank, carl, james, evan, mike|...].
This is because your rule is true for anything in the first position. If you had more clauses this could be meaningful because one of the other clauses could say something about this argument, but lacking that it really does mean anything. So your predicate could just as well be written:
path(X, P) :- findall(X, interact(_, X), P).
Every _ is a completely unique binding; they do not influence each other at all, so if you were hoping for an effect there you'd want something more like this:
path(F, X, P) :- findall(X, interact(F, X), P).
And you see right away that this doesn't help you much:
?- path(ella, X, P).
P = [james, tyrone].
So let's solve the problem already.
person(X) :- interact(X, _) ; interact(_, X).
This is just a helper that returns everybody whether they're on the left or right of the interaction.
path(Originator, Path) :-
setof(X, person(X), People),
path(Originator, Path, People).
This helper gets you a path from a particular person. We are relying on a helper function I'll show in just a second. We prune the possibilities to something reasonable by starting off with the list of all people. That way we can just choose the next person from the list of people we haven't examined yet, and we don't have to worry about cycles or too much recursion.
path(Originator, [], _).
path(Originator, [NextPerson|Rest], Considering) :-
select(NextPerson, Considering, RemainingToConsider),
interact(Originator, NextPerson),
path(NextPerson, Rest, RemainingToConsider).
The first clause says, we can always be done. The path from the originator to nobody is the empty path. This is a base case for our induction.
The second clause says, choose someone from the list of people we have left to consider. That someone interacted with the originator. Now find a path from that person through the people remaining to be considered. (select/3 unifies the third argument with the second argument without the first argument).
Let's look at a run:
?- path(ella, X).
X = [] ;
X = [james] ;
X = [james, ben] ;
X = [james, carl] ;
X = [james, carl, evan] ;
X = [james, carl, evan, kelly] ;
X = [james, carl, evan, kelly, ben] ;
X = [james, carl, evan, kelly, frank] ;
X = [james, carl, evan, mike] ;
X = [james, carl, evan, mike, frank] ;
X = [james, frank] ;
X = [tyrone] ;
false.
Now, in your original question you said something about Ben and Frank and not being interested in the other paths. I still don't see a logical reading that will distinguish between those cases, but you can at least find all the longest paths, like this:
longest_paths(Originator, Path) :-
path(Originator, Path),
\+ (path(Originator, Path2),
length(Path, MaxLen),
length(Path2, NextLen),
NextLen > MaxLen).
This isn't terribly efficient, but what it says is, find me a path Path from Originator, such that there is no other path with a longer length. This finds us three solutions:
?- longest_paths(ella, X).
X = [james, carl, evan, kelly, ben] ;
X = [james, carl, evan, kelly, frank] ;
X = [james, carl, evan, mike, frank] ;
false.
And this is as close as I think I can get you to the solution you want. I hope it helps!

Related

Use a rule if a fact doesn't exist in prolog?

I'm new to prolog.
The idea of my project is to say "The room X is free if none is a guest of X, while X is taken if a family lives in X".
I use a predicate
guest(FamilySurname,RoomTaken)
So this mean that a family has taken that room.
taken(X) :- guest(_,X).
So if a family lives in room X, then X is taken.
My problem is how could i say that the room X is free? I should use a kind of NOT, like:
free(X) :- "NOT EXIST" guest(_,X).
How could i translate that "NOT EXIST" in prolog?
I have tried with ! but it doesn't work properly... maybe I'm placing it in the wrong way.
Sorry for my english.
Check the following code:
taken(X, Y) :- guest(Y,X).
free(X) :- \+ guest(_,X).
I made the a little change to taken, now it shows you who is on that room.
guest(albert, 123).
?- taken(123, R).
R = albert.
And the free() predicate it's pretty straightforward, I used the negation operator, you can check How to negate in Prolog
The code in the first answer does not seem to be a solution, i.e. the query ?- free(X) will not give an answer, where as the query ?- free(foo) will be successful. One needs to consider query floundering in Prolog into account, i.e. all variables
Consider the following code in which two alternatives for presenting the free predicate.
room(123).
room(124).
guest(albert, 123).
taken(Room) :- guest(_, Room).
free2(Room) :- room(Room),
\+ taken(Room).
free(Room) :- room(Room),
forall(guest(_, RoomTaken),
RoomTaken \= Room).

Prolog: deduction given that items can be in exactly one of two sets, with set sizes known

I have 5 people in a room. I'll be writing rules to determine whether the people are happy or sad. However, before I even start with that, I have the overlying knowledge that - of the 5 - exactly 3 are happy and 2 are sad (and none can be both). It should therefore be possible to make deductions based on this: if - by any means - I know who the three happy people are, then I can deduce the two sad people, and vice versa.
What I've got so far is as follows:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(X) :-
person(X),
\+ sad(X),
findall(Y, sad(Y), YS),
length(YS, 2).
When asked happy(X), Prolog will give me Roy, Steve and Jack, because it already knows who the two sad people are. Problem: I'm unable to define a sad/1 rule in the same manner, because of the mutual recursion with happy/1. I want to be able to add in rules such that the outcome in the above example remains the same, yet the following initialisation would list Bob and Tim as sad:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
happy(steve).
happy(roy).
happy(jack).
Is there a better way I should be thinking about this? It's important that I'll be able to go on to later write more rules for sad/1 and happy/1, adding additional logic beyond the fact that deduction should be possible based on the knowledge that the 5 are split into 3 happy and 2 sad.
How about using clpb?
:- use_module(library(clpb)).
Sample query:
?- Hs = [Bob,Tim,Steve,Roy,Jack],
sat(card([3],Hs)), % exactly three are happy.
(
Who = sad, sat(~H_bob * ~H_tim) % specify the sad ones ...
; Who = happy, sat(H_jack * H_roy * H_steve) % ... OR the happy ones?
),
labeling(Hs).
Who = sad, Bob = 0, Tim = 0, Jack = 1, Roy = 1, Steve = 1, Hs = [0,0,1,1,1]
; Who = happy, Bob = 0, Tim = 0, Jack = 1, Roy = 1, Steve = 1, Hs = [0,0,1,1,1].
Sorting the logic out is a matter of consistency and avoiding conflicting meanings of a given predicate or fact.
Your definition of sad/1 is currently a fact that results in one result for each backtrack to the query, sad(X). But your definition of happy/1 generates a list. That leaves you with how you want to define sad/1 to generate a list, which would be in conflict with your current definition of sad/1 as a query that is true if the argument is a sad person.
A more consistent approach would be define happy/1 to behave the way sad/1 behaves:
happy(X) :-
person(X),
\+ sad(X).
Then you can define your list versions:
happy_all(A) :-
findall(X, happy(X), A).
sad_all(A) :-
findall(X, sad(X), A).
Now the above assumes you have explicit facts for person/1 which defines the universe of all valid people, and sad/1 which defines who the sad ones are. It also assumes that if a person isn't sad, then they must be happy.
You could flip this around and explicitly define happy people with happy/1 facts, then define sad/1 in terms of people not being happy assuming that a person must be happy if they aren't sad:
sad(X) :-
person(X),
\+ sad(X).
And the happy_all/1 and sad_all/1 predicates will still apply.
If you want to mix your facts with happy/1 and sad/1, this can create a consistency issue: (1) cases where a person isn't defined as happy or sad... then what are they? and (2) what if a person is defined as both happy and sad?
Semantically, you may want to define both sad/1 and happy/1 explicitly if you are also allowing for someone not being either happy or sad. You can do this:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(steve).
happy(roy).
happy_all(A) :-
findall(X, happy(X), A).
sad_all(A) :-
findall(X, sad(X), A).
But not define predicates for happy/1 or sad/1 since they are already facts. That keeps things simple. We just don't know if jack is happy or sad.
But what if we want to say that if a person isn't happy or sad, then they must be happy and add that rule back in. To avoid the looping you mentioned, we don't to be mixing rule names with fact names. In that case:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(steve).
happy(roy).
% A person is happy if they are, in fact, happy
happy_person(X) :-
happy(X),
% A person is happy if they are neither happy or sad
happy_person(X) :-
person(X),
\+ happy(X),
\+ sad(X).
% A person is sad if they are, in fact, sad
sad_person(X) :-
sad(X).
% Who are all the happy people?
happy_all(A) :-
findall(X, happy_person(X), A).
% Who are all the sad people?
sad_all(A) :-
findall(X, sad_person(X), A).

Prolog Chaining winners?

I'm trying to compare two people and from those 2 people if the person had played someone before and won then lost to the newer person then that person technically is above from everyone else.
For example, It's set up like this:
Example of how it's set up: winner(won, lost).
winner(john, jacob).
winner(mike, john).
winner(scott, mike).
winner(matt, scott).
winner(X, Z) :- winner(X, Y), winner(Y, Z).
If I call: winner(matt, mike). It'll return true since matt beat scott which means he also beats mike since mike lost to scott.
Essentially I want to be able to call winner(matt, jacob). and it'll return true.
I have it only querying on tier down with that current rule, how would I go about querying through unlimited tiers? I'm confused on how to approach this.
You need two predicates. One for the basic facts, winner/2, and another for the transitive relation. For example:
transitive_winner(X, Y) :-
winner(X, Y).
transitive_winner(X, Z) :-
winner(X, Y),
transitive_winner(Y, Z).
With this definition and your winner/2 facts, you can ask e.g.
?- transitive_winner(mike, Y).
Y = john ;
Y = jacob ;
false.
You need to be careful when defining this transitive relations, however, to avoid left-recursion as in:
transitive_winner(X, Z) :-
transitive_winner(Y, Z),
winner(X, Y).
In Prolog systems that don't support tabling, a call to transitive_winner/2 would result in a stack overflow error.

Prolog negation with more solutions

I've got a little problem and don't know where to find a solution. You probably heard of problem of flying birds:
bird(eagle).
bird(penguin).
can_fly(penguin):-!,fail.
can_fly(X):-bird(X).
I tried to modify and use this knowledge for some "love story". Imagine this
maried(a, b).
maried(c, d).
lovers(a, d).
likes(X, Y):-maried(X, Y).
Now I want to say something like "If X is maried with Y, but X is Z's lover, then X doesn't like Y, but likes Z".
I tried this:
likes(X, Y) :- lovers(X, Y).
likes(X, Y) :- maried(X, Y), lovers(X, _),!,fail.
likes(X, Y) :- maried(X, Y).
It works unless I want to evaluate a goal
likes(A, B).
If there is more facts in the database and Prolog finds first cheater, it will stop backtracking and I can't find any other solution. Maybe it will be obvious after, but now I have nothing on my mind..
Thanks in advance (and maybe sorry for my English :))
Already your first example is not too useful. You can ask:
is the eagle a bird?
is the penguin a bird?
can the eagle fly?
can the penguin fly?
but you can't even ask, "which birds can fly?":
?- can_fly(Bird).
false.
If you would want to be able to ask more general question, like, "which birds can fly?", or "which birds cannot fly?", you would need to either explicitly list the flying or non-flying birds. As most birds can fly, let's list explicitly the non-flying birds:
bird(eagle).
bird(penguin).
bird(ostrich).
bird(dodo).
bird(sparrow).
bird(pigeon).
flightless_bird(penguin).
flightless_bird(ostrich).
flightless_bird(dodo).
bird_of_pray(eagle).
extinct(dodo).
extinct(wooly_mammoth).
can_fly(Bird) :-
bird(Bird),
\+ flightless_bird(Bird).
extinct_bird(Bird) :-
bird(Bird),
extinct(Bird).
Note the use of the ISO predicate for negation, \+/1. It is true if the goal cannot be proven. It is much cleaner than the fail-cut combination you are using.
How you organize your knowledge is a totally different question. The example I have given is incomplete and not necessarily the best way to do it. As a general rule, you should try to keep your data in a normalized form, with facts and clauses of facts playing the role of tables and table rows.
Hopefully this answer points in the right direction.
The cut-fail thing is an antipattern that's misleading you. It would be much better to say something in the original like this:
bird(eagle).
bird(penguin).
flightless(penguin).
can_fly(X) :- bird(X), \+ flightless(X).
See #boris's answer for an excellent and much more detailed discussion of this problem.
So, you have this database. I'm going to put names in so that I can comprehend it a little better.
married(bill, hillary).
married(barack, michelle).
lovers(bill, michelle).
Now what you want to say is that married people like each other, unless they're cheating. Well, it would be better to define these terms in Prolog so we can reason with them. Create the language we need to use to define our domain. That's going to look like this:
cheating(Cheater, SpitedSpouse, Lover) :-
married(Cheater, SpitedSpouse),
lovers(Cheater, Lover),
SpitedSpouse \= Lover.
Now it's much easier to define likes/2!
likes(Husband, Wife) :- married(Husband, Wife), \+ cheating(Husband, Wife, _).
likes(Husband, Lover) :- cheating(Husband, _, Lover).
We can even define dislikes properly:
dislikes(SpitedSpouse, Lover) :- cheating(_, SpitedSpouse, Lover).
dislikes(Cheater, Spouse) :- cheating(Cheater, Spouse, _).
Look at what we've learned: what you really have is a marriage relationship between two people, or a cheating relationship between three people, and of your other predicates are just projections of these two fundamental relationships. That's pretty neat, yeah? :) cheating/3 is basically the classic "love triangle." That suggests ways we could evolve the program to be more potent: handling love quadrilaterals, of course! And so forth.
Notice something interesting? This code can't "catch" michelle—isn't she just as culpable as bill? This underscores a bigger problem: the gender/orientation limitation of the predicates. I think it helps to have concrete nouns like these to see the logical relationships, but it's a dangerous shortcut that should be resolved. An easy way to handle the logic would be to create a predicate like this:
spouse(X, Y) :- married(X, Y)
spouse(X, Y) :- married(Y, X).
Of course you then have to do more checks to make sure you don't have the same people repeated, but it's not hard, and then change your variables to be less gender-specific.

Why does Prolog crash in this simple example?

likes(tom,jerry).
likes(mary,john).
likes(mary,mary).
likes(tom,mouse).
likes(jerry,jerry).
likes(jerry,cheese).
likes(mary,fruit).
likes(john,book).
likes(mary,book).
likes(tom,john).
likes(john,X):-likes(X,john), X\=john.
Hi there, above is a very simple prolog file, with some facts and only one rule: John likes anyone who likes him.
But after loading this file and ask Prolog the following query:
likes(john,X).
The program crashes. The reason is somehow prolog gets stuck at likes(john,john) even though the rule states that X\=john.
Any advice?
Ironically, given the site we're on, you're getting a stack overflow.
It does this because of the order of execution that prolog uses, it's going to go into an infinite recursion at likes(X,john) in your rule, it activates the rule again - not a fact - never getting to the X\=john bit.
One way to fix this is to have your rule named differently from your fact like this:
kindoflikes(tom,jerry).
kindoflikes(mary,john).
kindoflikes(mary,mary).
kindoflikes(tom,mouse).
kindoflikes(jerry,jerry).
kindoflikes(jerry,cheese).
kindoflikes(mary,fruit).
kindoflikes(john,book).
kindoflikes(mary,book).
kindoflikes(tom,john).
likes(Y,X):- kindoflikes(X,Y), X\=Y.
likex(Y,X):- kindoflikes(Y,X), X\=Y.
Note the reversal of X and Y in the kindoflikes in the two rule definitions.
So you get:
?- likes(john,X).
X = mary ;
X = tom ;
X = book.
But you're not locked into finding what john likes, and you can do:
?- likes(jerry,X).
X = tom ;
X = cheese.
Your first question was why your program crashes. I am not sure what kind of Prolog system you are using, but many systems produce a clean "resource error" which can be handled from within Prolog.
Your actual problem is that your program does not terminate for the query likes(john, X). It gives you the expected answers and only then it loops.
?- likes(john,X).
X = book
; X = mary
; X = tom
; resource_error(local_stack). % ERROR: Out of local stack
You have been pretty lucky that you detected that problem so rapidly. Imagine more answers, and it would have not been that evident that you have the patience to go through all answers. But there is a shortcut for that. Ask instead:
?- likes(john, X), false.
This false goal is never true. So it readily prevents any answer. At best, a query with false at the end terminates. Currently this is not the case. The reason for this non-termination is best seen when considering the following failure-slice (look up other answers for more details):
?- likes(john,X), false.
loops.
likes(tom,jerry) :- false.
likes(mary,john) :- false.
likes(mary,mary) :- false.
likes(tom,mouse) :- false.
likes(jerry,jerry) :- false.
likes(jerry,cheese) :- false.
likes(mary,fruit) :- false.
likes(john,book) :- false.
likes(mary,book) :- false.
likes(tom,john) :- false.
likes(john,X) :-
likes(X,john), false,
X\=john.
So it is this tiny little part of your program that is responsible for the stack overflow. To fix the problem we have to do something in that tiny little part. Here is one: add a goal dif(X, john) such that the rule now reads:
likes(john,X) :-
dif(X, john),
likes(X,john).
dif/2 is available in many Prolog systems like: SICStus, SWI, YAP, B, IF.

Resources