This homework will be carried out within the programming environment of the Progol system. In the first portion, you will use Progol to execute defini- tions that you will write as definite-clause statements. In the portion after that, you will use Progol to do much more. For example, you will see how to automatically construct the definite-clause definitions of functions from partial specifications of their input-output behaviour. It is impor- tant that you become familiar with Progol.
Make sure you can start Progol. The executable is: ~cs731-1/public/progol/progol
If you are successful, you should see a prompt that looks something like: CProgol Version 4.2 |- From now on, I will refer to: |- as the Progol prompt. At this prompt, Progol will accept commands, almost all of which end with a "?" mark.
Try: help?
This should give you something like: The following system predicates are available: ! , /2 -> /2 ; /2 < /2 = /2 =.. /2 =< /2 == /2 > /2 >= /2 @< /2 @=< /2 @> /2 @>= /2 + /1 = /2 == /2 advise/1 any/1 arg/3 asserta/1 bagof/3 clause/2 clause/3 commutative/1 commutatives constant/1 consult/1 determination/2 element/2 float/1 functor/3 generalise/1 help help/1 int/1 is/2 length/2 listing listing/1 modeb/2 modeh/2 modes name/2 nat/1 nl noreductive nosplit nospy not/1 notrace number/1 op/3 ops otherwise parity read/1 reconsult/1 record/2 reduce/1 reductive repeat retract/1 retract/2 see/1 seen set/2 setof/3 settings sort/2 spies split spy/1 stats tab/1 tell/1 test/1 told trace true var/1 write/1 Help for system predicates using help(Predicate/Arity) yes [:- help? - Time taken 0.02s] Progol is terminated by depressing the Control and D keys together (^D).
A 1-bit adder circuit looks like this:
Vo | | | ____|______ | | | | Carry ___| |_____Carry in out | | |__________| | | | | | o | o | /_\ | /_\ |__| |__| | | | | A B
The truth-table specification for this adder is:
A B Cin Vo Co 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 1 1 0 1 1 0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 The 1-bit adder can be represented as definite clauses in propositional logic.
For example, the following propositions can be used to denote the input: 1) a, b, c_in: true if A or B or Cin are true, respectively; and 2) not_a, not_b, not_c_in: true if A or B or Cin are false, respectively. Propositions used to denote output are: 1) v_o, c_o: true if Vo or Co are true, respectively; and 2) not_v_o, not_c_o: true if Vo or Co are false, respectively; and The following definite-clauses encode the truth table for the adder:
v_o:- not_a, not_b, c_in. v_o:- not_a, b, not_c_in. v_o:- a, not_b, not_c_in. v_o:- a, b, c_in. not_v_o:- not_a, not_b, not_c_in. not_v_o:- not_a, b, c_in. not_v_o:- a, not_b, c_in. not_v_o:- a, b, not_c_in. c_o:- not_a, b, c_in. c_o:- a, not_b, c_in. c_o:- a, b, not_c_in. c_o:- a, b, c_in. not_c_o:- not_a, not_b, not_c_in. not_c_o:- not_a, not_b, c_in. not_c_o:- not_a, b, not_c_in. not_c_o:- a, not_b, not_c_in.
(a) Type these clauses into a file, say adder.pl, using your favourite editor.
(b) Also include definitions for the current state of the inputs A, B and Cin when switch A is "on", and B, Cin are "off".
(c) Start Progol. At the prompt, consult your definitions in with the command:
consult(adder)?
To check if Progol has the correct definitions, first try the command: listing? Progol's response should be something like: The following user predicates are defined: a c_o not_b not_c_in not_c_o not_v_o v_o [Total number of clauses = 19] yes [:- listing? - Time taken 0.00s] You can check individual clause definitions by the "listing" command. For example: listing(not_c_o)? will list the definitions for the proposition not_c_o. You are now in a position to investigate the consequences of the switch settings. This is done by examining whether which of the "output" propositions v_o, not_v_o, c_o, not_c_o are logical consequences of the set of definite-clauses in adder.pl and inputs.pl. Progol can be asked if v_o is a logical consequence by typ- ing the following at the prompt: v_o? to which should reply yes
(a) Verify (using the truth-table) that Progol gives correct an- swers for the logical status of each the four output propos- itions.
(b) Can you simplify any of the definitions further. That is, do you think the adder circuit could be represented with either fewer, or simpler clauses?
Let us return to the 1-bit adder. Recall that separate pro- positions were needed for both an output and its negation (for example, v_o and not_v_o). Why did we have to be so clumsy? If v_o is a logical consequence of the clauses, then should we not be able to state confidently that not_v_o can- not possibly be a consequence as well?
The answer is yes, provided one assumption holds. This is that the definitions we use are not just correct, but com- plete. In other words, we have not left out any condition(s) which are true. This assumption is called the closed world assumption or CWA. Once you have complete and correct definitions for v_o and c_o, you can dispense with the definitions for not_v_o and not_c_o. Instead, you can use a predicate called not. The complete set of definitions for the adder is now as fol- lows:
v_o:- not(a), not(b), c_in. v_o:- not(a), b, not(c_in). v_o:- a, not(b), not(c_in). v_o:- a, b, c_in. c_o:- not(a), b, c_in. c_o:- a, not(b), c_in. c_o:- a, b, not(c_in). c_o:- a, b, c_in.
The new symbol not is a special metalogical predicate built-in to Progol, that implements negation under the closed world assumption. Any of the propositions a, b, or c_in that are known to be true have to be in this file. If any proposition, say c_in is not in the file, then, by CWA, not_c_in is true.
Consult your new (smaller) set of definitions. You can now examine if not_v_o or not_c_o are logical consequences of the definitions of your definitions by asking: not(v_o)? or not(c_o)? Work through the truth table again, verifying the conse- quences of your adder definitions and truth value assign- ments to a, b, and c_in.
One way to represent sets is with monadic predicates. The set S = {a, b, c,...} is represented by the predicates s(a), s(b), etc. Assume for the moment, that there are only 2 sets of interest in the world, whose elements are represent- ed by the monadic predicates p/1 and q/1. Note: You will keep coming up against the notation Name/Number (like p/1). By this, it will be understood that we are talking about a predicate with a particular name, and with some number of arguments. Set operations with this monadic representation are easy. Consider the union operator. Union of P and Q is a set p_union_q = {e| e in P or e in Q} Or, as definite-clauses:
p_union_q(X):- p(X). p_union_q(X):- q(X). Instead of having a new unary predicate for each set, it is possible to represent sets with predicates that have the name of the set as first argument. For example, the sets P = {a,b} and Q = {1,2} can both be represented with a binary predicate elem/2, as elem(a,p), elem(b,p), elem(1,q), and elem(2,q). The statement: x in S1 U S2 iff x in S1 or x in S2 can now be represented by the following definite clauses: in_union(X,S1,S2):- elem(X,S1). in_union(X,S1,S2):- elem(X,S2).
Write definite clauses for the following statements: x in S1 & S2 iff x in S1 and x in S2 <x,y> in S1 x S2 iff x in S1 and y in S2 x in S1 \ S2 iff x in S1 and x not in S2
Let N5 = {0,1,2,3,4}, the set of the first 5 natural numbers. The successor function that associates a natural number with another one, namely the next one can be represented as a set of ordered pairs. For the set N5: successor5 = {<x,y> | x in N5, y in N5, and y = x + 1} Later we will look at representing functions like addition (+). For the moment, the ordered pairs in successor5 can be represented by the following "extensional" definition: successor5(0,1). successor5(1,2). successor5(2,3). successor5(3,4).
(a) Write a similar extensional definition for the less_than5 relation, that describes all ordered pairs (x,y) such that the first element in the pair is less than (lt) the second. (b) Given sets A, B with elements from N5 write a definition for the binary relation: lt_AxB = {<x,y> | <x,y> in AxB, and <x,y> in less_than5}
Consider the map below: ___________________________________ | 1 | | | | ______________________ | | | 2 | 5 | | | | ______ | | | | | | | | | | | |___| 4 |____| | | | | |______| | | | | | |______| | | | 3 | 6 | | | |_______________|______| | |___________________________________| We want to colour this map with the four colours red, green, blue and yellow. No two adjacent countries are to have the same colour. The colourings allowed can be considered exten- sionally as a set of ordered 6-tuples, with each entry representing a colour for each region. Consider the following sets: Colour = {red, green, blue, yellow} DiffColour = {<x,y> | x in Colour and y in Colour, and x =/= y} The set Map can be defined as a set of ordered 6-tuples, where each argument corresponds to a colour for that region, and adja- cent regions have a different colour. Somewhat informally: Map = {<x1,x2,x3,x4,x5,x6> | x1..x6 in Colour and <xi,xj> in DiffColour for all i and j that share a border}
:- set(h,200)?
The reason for this is the following. While most Prolog interpreters allow you
to enter an infinite recursion, the interpreter in PROGOL will not allow this.
It places a bound h on the number of resolution steps in any search for a proof
(refutation). The command above increases the default setting for h so that the
execution is not prematurely terminated.
(a) Write definitions for Colour using a monadic predicate. (b) Write definitions for DiffColour using a binary predicate. (c) Now complete this definition for Map: map(X1,X2,X3,X4,X5,X6):- ..... (d) Check an assignment of colours to regions 1..6 is consistent with the requirements.
(e) In fact, you should be able to do better than just check a particular assignment. What does the following query accomplish: map(C1,C2,C3,C4,C5,C6).
Enter the following definitions: bachelor(X):- not(married(X)), male(X). married(john). male(john). male(bill).
(a) Does the following query succeed correctly: bachelor(X) (b) What about: bachelor(bill) and bachelor(john) (c) What happens if the definition of bachelor was written as:
bachelor(X):- male(X), not(married(X)).
We now have a rule that MUST be followed when using not: There should be NO uninstantiated variables in the call to not. Prolog's theorem-prover is not guaranteed to be correct if this rule is not followed.
Suppose the edges in a graph can be represented by the predicate g/2. In particular, consider this graph: g(1,2). g(1,3). g(2,4). g(2,5). g(3,4). g(4,7). g(5,6). g(6,7). The transitive closure of a directed graph has an edge wher- ever the graph has a path. These are all definitions of transitive closure: Definition 1: tc(X,Y):- g(X,Y). tc(X,Y):- g(X,Z), tc(Z,Y). Definition 2: tc(X,Y):- g(X,Y). tc(X,Y):- tc(X,Z), g(Z,Y). Definition 3: tc(X,Y):- tc(X,Z), g(Z,Y). tc(X,Y):- g(X,Y).
(a) Which of the following are true for each of the preceding definitions: tc(3,6) tc(3,7) (b) For which values of X (and Y) are the following true: tc(X,5) tc(5,X) tc(X,Y) (c) For the following definition of a graph:
g(1,2). g(1,3). g(2,3). g(3,4). g(4,1). What are the first 10 answers returned by Progol for tc(1,X) using Definition 1 for transitive closure? Note: You can force Progol to enumerate all X's for which tc(1,X) is true by typing a semi-colon (";") after each answer returned.
In this exercise, you will see how you can execute the de- finitions of simple arithmetic functions. It is possible to treat numbers as any other constant, and functions and predicates on them just like any other predicates. But the naming scheme of Zero = the constant symbol 0, One = s(0), Two = s(s(0)) is cumbersome. It would be much better to name them using the standard Indo-Arabic conventions. Progol al- lows this. Next, what do we do with predicates for comparing numbers. Do we declare them explicitly each time, for the numbers that we want, or do we "pre-load" these into Progol. If so, how many entries do we consider pre-loading (comparisons of all integers upto 2^32 perhaps?) For all practical purposes, you may assume that Progol has access to a (nearly) infinite table of such facts. How it does it is not really our concern here. Sufficient to say that, given a pair of numbers, Progol "looks-up" its inter- nal table, and returns the truth-value of the comparison. Progol also has the built-in functions +,-,* and /. There is one more predicate that you may not have spotted. is/2 will be used often when dealing with numbers in Pro- gol. This predicate is used to evaluate arithmetic expres- sions.
(a) To understand is/2, try entering the following at the Progol prompt: X = 2 + 3? (b) Now try: X is 2 + 3?
This should immediately give you an idea of what is/2 does (and for that matter, what =/2 does'nt do when dealing with arithmetic expressions). So, the moral is as follows: if you want an arithmetic ex- pression to be evaluated, then you MUST use is/2. Those of you who are used to programming in imperative languages like Pascal, Fortran, C etc. may also walk into another trap. Now it is quite common in those languages to decrement the value of a variable, and assign the new value back to the same variable. For example, in Pascal: x := 2 + 3; x := x - 1; leaves the variable x with the value 4. Now from what you have just seen, you know that the is/2 predicate has to be used to achieve the effect of :=.
(a) Write a definition for delete(X,L,L1): delete(X,L,L1) is true for all X,L,L1 s.t X is in L, and L1 is the list L with an occurrence of X removed. So: delete(a,[1,a],[1]) is true delete(a,[1,a,a], [1,a]) is true delete(a,[1,a,a],[1]) is false delete(a,[1,b],[1,b]) is false
(b) Here is a definition of permute: permute([],[]). permute(L,[X|P]):- delete(X,L,L1), permute(L1,P). Consider the list [1,2,3,4]. How many permutations of this list are possible? Verify that the definition of permute does indeed generate all the combinations.
Here is a problem originally posed by Daniel Ligett of the Wang Institute, Tyngsboro MA. On a street, there are five houses, each of a different color and inhabited by men of different nationalities, with different pets, drinks and cigarettes. You are told the fol- lowing: 1. The Englishman lives in the red house. 2. The Spaniard owns the dog. 3. Coffee is drunk in the green house. 4. The Ukrainian drinks tea. 5. The green house is immediately to the right (your right) of the ivory house. 6. The Winston smoker owns snails. 7. Kools are smoked in the yellow house. 8. Milk is drunk in the middle house. 9. The Norwegian lives in the first house on the left. 10. The man who smokes Chesterfields lives in the house next to the man with the fox. 11. Kools are smoked in the house next to the house where the horse is kept. 12. The Lucky Strike smoker drinks orange juice. 13. The Japanese smokes Parliaments. 14. The Norwegian lives next to the blue house.
(a) Who owns a zebra?
(b) Who drinks water?
Give the full program for solving the puzzle.
Hints: The trick to most of these puzzles is to choose the correct representation. The easiest one is to represent the street as a list of elements, each of the form: house(Colour, Nationality, Pet, Drink, Cigarette) One possible overall structure to your program is thus (here the "_" is like a "don't care") puzzle(Animal,Owner,Drink,Drinker):- Street = [ house(C1,N1,P1,D1,S1), house(C2,N2,P2,D2,S2), house(C3,N3,P3,D3,S3), house(C4,N4,P4,D4,S4), house(C5,N5,P5,D5,S5) ], (fill-in constraints 1-14), member(house(_,Owner,Animal,_,_),Street), member(house(_,Drinker,_,Drink,_),Street). You can largely write all the constraints using the member/2 predicate. For others, you may need to define a new next_to predicate
By now, you must have a fair idea of how to use Progol to execute logic programs written by you. Inductive Logic Pro- gramming (ILP) is about automating the construction of logic programs from partial specifications of their behaviour. For example, given a partial specification of true and false statements of the mem/2 predicate: TRUE FALSE mem(0,[0]) mem(0,[1]) mem(1,[1]) mem(1,[0]) mem(0,[0,0]) mem(3,[]) mem(0,[0,1]) mem(2,[3]) mem(1,[0,1]) mem(0,[1,0]) mem(3,[4,2,3]) An ILP program then constructs automatically the definition: mem(A,[A|B]). mem(A,[B|C]):- mem(A,C).
Try: help?
This should give you something like: The following system predicates are available: ! , /2 -> /2 ; /2 < /2 = /2 =.. /2 =< /2 == /2 > /2 >= /2 @< /2 @=< /2 @> /2 @>= /2 + /1 = /2 == /2 advise/1 any/1 arg/3 asserta/1 bagof/3 clause/2 clause/3 commutative/1 commutatives constant/1 consult/1 determination/2 element/2 float/1 functor/3 generalise/1 help help/1 int/1 is/2 length/2 listing listing/1 modeb/2 modeh/2 modes name/2 nat/1 nl noreductive nosplit nospy not/1 notrace number/1 op/3 ops otherwise parity read/1 reconsult/1 record/2 reduce/1 reductive repeat retract/1 retract/2 see/1 seen set/2 setof/3 settings sort/2 spies split spy/1 stats tab/1 tell/1 test/1 told trace true var/1 write/1 Help for system predicates using help(Predicate/Arity) yes [:- help? - Time taken 0.02s]
1. Language constraints:
these control the sort of
definitions that are
constructed for the target
predicate.
2. Background knowledge:
these are definitions of
other relations that may
be useful in constructing
the definition of the target
predicate.
3. Positive and negative examples of the target predicate:
these act as a partial specification
of the relation
specified by the target
predicate.
For Progol these three bits of information are usually pro-
vided in a single file. As always, the file's name
should
have a ".pl" suffix.
Consider a simple problem of learning to classify an animal based on the following observable attributes: What covering does the animal have? Does it have milk? Is it warm blooded? Where does it live? Does it lay eggs? Does it have gills? Progol statements that specify this are in the file:
~cs731/public/Examples/animals.pl
(a) Link or copy this file to your directory.
(b) Examine this file and note that:
1. The language constraints look like: :- some_name(.....)? 2. The background knowledge is a set of clauses 3. Positive examples look like: target_predicate_name(....). 4. Negative examples look like: :- target_predicate_name(....).
Consider Progol's task when acting as an ILP system. The job is to construct a set of clauses to act as a definition for the target predicate. All positive examples provided must be derivable from this set of clauses, and the set must be consistent with the negative examples. Later, we will look at the logical constraints that such a clause set must satisfy. For now, consider what each clause constructed by Progol would look like. It would typically be a definite clause like: target_predicate(.....):- body_literal_1(....), body_literal_2(....),.... . Which predicate symbols would we like to appear in the head or body of the clause? Do we know anything about the argu- ment types of such literals (for example, are they always natural numbers)? Do we require some arguments for a literal to be bound before it can appear in the clause (for example, recall the constraint on not/1 )? In effect, by stating such preferences, we are really defin- ing a language for clauses to be constructed by Progol. The primary means of doing this is via mode declarations. There are two sorts of mode declarations. Those that look like: :- modeh(RecallNumber,Template)? and those that look like: :- modeb(RecallNumber,Template)? Ignore the RecallNumber for the moment. Using a template (which we will look at shortly), the first of these (the modeh one) describes legal forms of literals that can appear in the head of a Progol clause. The second type of mode de- claration (the modeb one) describes legal forms of literals that can appear in the body of a Progol clause. Now look at the modeh declaration in animals.pl. :- modeh(1,class(+animal,#class))? In this the template provided for a head literal is: class(+animal,#class) What this says is that any clause that Progol constructs should be be of the form: class( , ):- Body. In fact, it does more than just this. It also says that the first argument is variable of type animal and the second is constant of type class (more on this in "Argument Annotations" below). So clauses constructed will look like: class(A,mammal):- .... class(A,fish):- .... (etc)
Which predicate symbols can appear in the body of clauses for class/2?
Progol checks the types of terms by using unary predicate definitions which are provided as background knowledge. That is, if Progol wanted to check if some constant, say mammal was of type class it checks to see if the call: class(mammal)? succeeds.
Is the following a legal type definition. Give reasons for your answer.
class(X):- member(X,[mammal,fish,reptile,bird]).
Notice that in the modes arguments are annotated by the symbols + and # . In fact, there are 3 such syntactic constraints on the arguments: +, -, and #. To under- stand these, you will need to realise that Progol constructs its clauses literal by literal, starting from the head literal. Clauses are constructed using the following rules:
1. A clause is of the form H:- B1, B2, ..., Bc. 2. Variables will appear in any arguments of H, Bi with + or - annotations. Constants will appear in any argu- ments annotated by #. By convention all + variables will be called input variables and all - variables will be called output variables. 3. Any input variable of type T in a body literal Bi (1 =< i =< c) must appear as an output variable of type T in a body literal Bj (1 =< j < i) or as an input variable of type T in H. 4. Any output variable of type T in H must appear as an as an output variable of type T in a Bi (1 =< i).
Consider the mode declarations:
:- modeh(1,class(+animal,#class))? :- modeb(1,has_milk(+animal))?
Are the following clauses allowed. Give reasons for your answer.
(a) class(A,B):- has_milk(B).
(b) class(A,B):- has_milk(A).
(c) class(A,mammal):- has_milk(platypus).
(d) class(platypus,mammal):- has_milk(platypus).
(e) class(A,mammal):- has_milk(A).
Finally, a note on the RecallNumber. This concerns the "determinacy" of the predicate with the template provided. In other words, it specifies the maximum number of times a call to the predicate would succeed, with a given set of in- put variables. This need not worry you too much. Usually, you will only be concerned with predicates with a recall number of either: 1: for a given set of input variables the output variables and constants are determinate. That is, a call to the predicate, with any particular set of bindings for these input variables, succeeds at most once. *: for a given set of input variables the output variables and constants are not determinate. That is, a call to the predicate, with any particular set of bindings for these input variables, succeeds some finite number of times.
Why is the recall number for habitat/2 a * ?
Progol constructs definitions within the "Mode Language" specified.
It is therefore important to be able to write mode declarations cor-
rectly.
Given the set of natural numbers N, let dec(X,Y): X in N and Y = X - 1 plus(X,Y,Z): X,Y in N and Z = X + Y mult(X,Y,Z): X,Y in N and Z = X*Y You may (recall this definition of mult/3: mult(0,A,0). mult(A,B,C):- dec(A,D), mult(D,B,E). plus(E,B,C). Assume that you had some predicate nat/1 that could be used to check if a variable was in N. Also assume that you were provided definitions for dec/2 and plus/3.
(a) Specify language constraints in the form of mode-declarations that allow the construction of this definition of mult/3.
Suppose Progol had constructed the following defini- tion for choosing "m" elements from "n" (m,n are natural numbers >=0): n_choose_m(A,B,C):- dec(B,D), dec(A,E), n_choose_m(E,D,F), multiply(F,A,G), divide(G,B,C). (b) What mode declarations could have allowed the construction of such a definition?
Consider learning the definition of list membership for lists of natural numbers. Progol is provided with the positive and negative examples of the form shown earlier . Assume the availability of some predicate nat/1 as before. (c) Write a definition of a predicate nlist/1 that can be used by Progol to check if a term is a list of natural numbers: (d) Is the language specified by the following mode declarations expressive enough for Progol to learn the definition of mem/2 given earlier ? :- modeh(1,mem(+nat,[+nat|+nlist]))? :- modeb(1,mem(+nat,+nlist))?
You are now in a position to run Progol on the problem of constructing definitions for classifying animals.
(a) Start Progol and consult the file animals.pl. (b) You can construct clausal definitions for the examples using the generalise/1 command. Use help/1 to see what this command does. (c) Which clauses have as head the predicate symbol class/2 (d) Construct a clausal definition for class/2 and determine the clau-
ses with head class/2.
(e) Why are some clauses in (c) missing in the definition in (d)
(f) Does the definition in (d) along with the background knowledge B,
logically entail every positive example.
In the directory: ~cs731-1/public/Examples you will find a postscript file called trains.ps Either print or view this file on screen. The file contains a figure published nearly 20 years ago by Ryszard Michalski (then at the University of Illinois). As in scientific discovery, it is required to conjecture some plausible Law, in this case governing what kinds of trains are Eastbound and what kinds Westbound.
We will look at constructing definitions for trains bound east using Progol. Incomplete statements for this problem are in the same directory in the file:
incomplete_trains.pl
Copy this file to your directory.
(a) Complete the mode declarations (missing parts are indicated by "..."). (b) The definitions of two trains are missing from the background knowledge. Identify these, and write clausal descriptions for them. (c) Use generalise/1 to construct a definition for eastbound/1.
So far, you have only looked at cases where only the type definitions appear to be non-ground. All other relevant predicates in the background knowledge have been ground unit clauses. This does not have to be so. Consider the problem of constructing a definition of a cyclic graph. This needs the notion of a path in a graph.
In the directory: ~cs731-1/public/Examples you will find a file called cyclic.pl
Link or copy this to your directory.
This is a Progol file for constructing a definition for cyclic/1 using a non-ground definition for "path".
Obtain a clausal definition for cyclic/1 using the examples provided.
Just as background knowledge can be non-ground, there is no particular need for examples to be ground unit clauses ei- ther. Consider the following as the negative examples in animals.pl :- class(X,mammal), class(X,fish). :- class(X,mammal), class(X,reptile). :- class(X,mammal), class(X,bird). :- class(X,fish), class(X,reptile). :- class(X,fish), class(X,bird). :- class(X,reptile), class(X,bird). :- class(eagle,reptile).
(a) Replace the negative examples in animals.pl withthe ones above (b) What does Progol return as a definition for class/2 (c) Now add the following to the negative examples: :- class(X,reptile). Is the resulting program consistent? Give reasons for your answer.