You have probably heard this simple logic puzzle before:
A farmer is returning to his farm after a long day of working in the fields. He has with him a fox, a chicken, and some grain. He must cross a small stream on his way back to the barn. At the stream, there is a canoe, in which he can transport at most one item across at a time. However, he cannot leave the fox alone with the chicken, or the fox will eat the chicken. Similarly, he cannot leave the chicken alone with the grain because the chicken will eat the grain. Devise a plan (sequence of actions) that the farmer can take to safely bring all of his possessions across the stream and continue on his way home.
The farmer/fox/chicken/grain problem (FFCGP) is a classic planning problem. A planning problem is one where, given the current state of the world (i.e., the farmer and all three items on this side of the stream and nothing on the other side of the stream), you must devise a plan---a series of actions that can be carried out in order---to get to a desired goal situation (i.e., nothing on this side of the stream and the farmer and all three items on the other side). Further, in order for your plan to work, it must satisfy a set of constraints. In this case, at no time during the sequence of actions can the fox be left alone with the chicken, or the chicken be left alone with the grain.
You job is to build a simple planning engine which can solve the FFCG problem and similar problems of the same nature. A similar planning problem you may have also run across is the missionaries and cannibals problem (MCP):
Three missionaries and three cannibals come to a river and find a boat that holds two. If the cannibals ever outnumber the missionaries on either bank, the missionaries will be eaten. How shall they cross?
The implementation language for this project is Prolog. You will be programming
in a logical programming style, as described in Chapter 16 of your textbook.
Since this program must be written in Prolog, appropriate use of
logic and declarative language features is expected.
Your implementation must adhere to the tenets of logic and declarative
programming. In addition, your program must utilize recursion and
to the fullest extent. You
may not use any versions of assert
, retract
,
record
, erase
, flag
, or any
of the other database features of Prolog. You may use the
predicates we used and developed in class.
As with prior programs, your solution must adhere to good programming style, including correct use of parameter passing, identifier naming, predicate length, commenting, and headers for each predicate.
Write your program as a Prolog predicate called
plan_transport
which matches the following signature:
plan_transport( Initial_State, Goal_State, Set_of_Invalid_Combinations, Drivers, Max_Plan_Length, Plan ) :- /* your definition goes here */.
This predicate defines acceptable plans for transporting a given
set of objects (the farmer and his items, a set of
cannibals and missionaries, or a collection of space colonists and
their supplies) from one location to another using a
vehicle (boat, space ship, airplane).
Without loss of
generality, let's call the two locations the "left" location and
the "right" location.
Then the Initial_State
is a two-item list which
describes the set of objects at the left location and the set
of objects at the right location. For the FFCGP where all items
start on the left side and nothing is on the right, we can describe the
initial state as:
[ [ farmer, fox, chicken, grain ], [] ]
Note that
order does not matter in listing the items on either side.
We can also leave one or more portions of the initial state or
goal state unbound, either to see what is "reachable" from a given
start state or to see where we "might have come from" if the goal
state is specified. At a minimum, however, all known objects must
be listed in the union of both sides of both the initial and goal states.
Similarly, the Goal_State
defines the final configuration
at which we wish to arrive (with everything moved to the right):
[ [], [ farmer, fox, chicken, grain ] ]
The
Set_of_Invalid_Combinations
is a list, each element of
which is a set of items that cannot be left together. For example:
[ [ fox, chicken ], [ chicken, grain ], [ chicken, fox, grain ] ]
Again,
order does not matter in an invalid combination. Listing
[fox, chicken]
as an invalid combination precludes
any situation throughout the whole plan where either location
contains exactly those two items (in either order,
if lists are used to record the items on each side).
The Set_of_Invalid_Combinations
is a required parameter,
and must not be unbound.
The
Drivers
parameter is also required to be bound.
It is a simple list of objects (atoms). Every time the transport
vehicle moves from one location to another, at least one of the
objects in this list must be in it (i.e., there must be a legitimate
Driver from this list to pilot the transport vehicle).
The
Max_Plan_Length
is also required to be bound
and must be a non-negative integer.
Any legitimate Plan
must contain Max_Plan_Length
moves or fewer.
If plan_transport
is called with an
unbound variable provided for the Plan
, then a satisfactory
plan will be generated. Through backtracking, all possible legal
plans should be generated. If a bound value is provided for the
Plan
, then the predicate will succeed if the given plan
satisfies the constraints (including Max_Plan_Length
) and
fail otherwise.
To put the pieces of the FFCGP example together, your planner might be called as follows:
?- plan_transport( [ [ farmer, fox, chicken, grain ], [] ], [ [], [ farmer, fox, chicken, grain ] ], [ [ fox, chicken ], [ chicken, grain ], [ chicken, fox, grain ] ], [ farmer ], 10, Plan ).
Then a legal plan follows this syntax (white space added for readability and lets use Prolog canonical ordering):
Plan = [ [ go_right, farmer, chicken ], [ go_left, farmer ], [ go_right, farmer, fox ], [ go_left, farmer, chicken ], [ go_right, farmer, grain ], [ go_left, farmer ], [ go_right, farmer, chicken ] ]
Plan = [ [ go_right, chicken, farmer ], [ go_left, farmer ], [ go_right, farmer, fox ], [ go_left, chicken, farmer ], [ go_right, farmer, grain ], [ go_left, farmer ], [ go_right, chicken, farmer ] ]A plan is a list of moves. Each move is itself a list starting with
go_right
or go_left
and followed
by one or two items being transported. Without loss of generality,
at the start of the plan the transport vehicle always starts at
the left location. Further, every move must include at least
one Driver (from the Drivers
list) and at most one
other object. The plan shown above is
one 7-move plan which satisfies the problem. It is not the only
solution satisfying the constraints.
Framing the MCP in this style is left as an exercise for your testing.
Note that your solution need not write
any results.
You are simply
writing a predicate which will succeed or fail, and that may bind
its parameters (e.g., the Plan
) as a byproduct.
Also, your solution need not find the shortest plan first,
or generate plans in any specific order. However, your solution
must be able to generate all possible unique plans which
satisfy the constraints through backtracking. In other words, it
must be
Also, your predicate can simply fail for logically inconsistent
inputs (e.g., the same atom is listed on both sides in the
initial state or on both sides in the goal state, atoms appearing
in one state are not present in the other, a bound non-negative value is
not provided for Max_Plan_Length
, a bound value is not provided
for the set of invalid combinations, no plan satisfying the constraints
exists, etc.).
Recall that you can "read" in Prolog predicates
from a file, by typing "[filename]." inside the SWI-Prolog session;
You can then test the loaded predicates.
Alternatively you can create a separate Prolog program to load your
program file and run a series of test cases in one stroke.
For example, consider a file named triple.pl
containing
the Prolog predicate triple/2
from homework #8. In order
to streamline testing this predicate, one may create the following
file, named tests.pl
.
[triple]. triple([1], X). triple([1,2], X). triple([1,2,3], X). triple([1,2,3,4], X). triple([1,2,3,5], X). ... ...Now to run the several test cases contained in
tests.pl
in batch, you can enter "pl < tests.pl" at the UNIX command prompt.
Your Prolog program is to be organized and submitted
electronically as a a single file through the
Web-CAT
Curator for automatic grading.
All documentation and identifying information should be placed in comments
within the single source file.
The comments at the beginning of the file should identify the assignment
and give your full name.
Every Prolog predicate should be
preceded by comments explaining its purpose, the meaning of
each parameter, and the general strategy of its implementation if
applicable. 80% of the score comes
from correct implementation and execution; 20% of the score comes from
(logical and declarative) programming style,
sound design, and good coding principles (comments,
readability). The system will begin accepting submissions on 11/17.
You will have unlimited submissions and we will grade the
final submission only. Remember we do not accept late assignments or
projects, unless you have already arranged something with us. Therefore,
please stay abreast of the deadline and plan your schedule accordingly.
And lastly, all work on this project is to be your own. There
are no group projects in this course.