11

How to write correctly the effect axiom for empty(b,t)-action using the predicate contains(b,l,t) The predicate evaluates True , if the bucket b holds l liters of water at time t.

empty(b,t): completely empties bucket b at time t. The effect of the transfer is visible at time t+1

transfer(b,b',t): transfers as much water from bucket b to bucket b' as possible without spilling any starting at time t. The effect of the transfer is visible at time t+1.

Bucket 1 is filled with water and holds 7 liters. Bucket 2 is empty and holds 3 liters. The target state is that b2 contains 1 liter of water.

I would say that the correct solution is:

to any b,t,l( empty(b,t) -> contains(b,l,t))

would this be correct or should I set the amount of liters to l= 5 , for example ?

mat
  • 40,498
  • 3
  • 51
  • 78
Mensch
  • 487
  • 7
  • 19
  • 4
    At least for Prolog, your question doesn't make sense. Try to formalize it in a programming language, to get a clue. – CapelliC Aug 01 '16 at 05:21
  • 4
    Perhaps you should explain first, what b, t, and l stand for and what the rule is supposed to do. Assuming t is a point in time, b stands for a bucket and l is an amount of water (in litres), you state: "if at some point t, any bucket is empty then any bucket has an arbitrary amount of water". But then an instance of your axiom is "At time t, if bucket b is empty, then bucket b contains 100 litres of water." which is a contradiction. Since contradictory axioms prove anything, you should not use them. – lambda.xy.x Aug 01 '16 at 15:12
  • @ CapelliC @lambda.xy.x updated the problem for better understanding. – Mensch Aug 01 '16 at 17:13

3 Answers3

6

For this problem, an explicit time is not necessary, so we will represent the history as a list of actions. On the other hand, you need to explicitly represent the state of your system, i.e. the current content of the three buckets. The reason is that Prolog datastructures (i.e. terms) cannot be changed, once they are created. Since there are a lot of meaningless terms, it is good practice to define datatypes first via an is_type/1 predicate. Because you will be using arithmetic at some point (when you pour water from one bucket to another), I will use arithmetic constraints instead of the ancient is/2 predicate.

:- use_module(library(clpfd)).

First we state that there are 3 buckets, represented by the atoms b1, b2 and b3:

is_bucket(b1).
is_bucket(b2).
is_bucket(b3).

Then we need to define our state. We just use a term buckets/3 where the first argument holds the capacity of b1 and likewise for the other two.

is_state(buckets(X,Y,Z)) :-
    % each bucket contains at least 0 liters
    [X,Y,Z] ins 0 .. sup.

All containers may not become negative, so we set their domain to range from zero to (positive) infinity.

Now what's an action? So far you described emptying and pouring:

is_action(empty(B)) :-
    is_bucket(B).
is_action(pour(From, To)) :-
    is_bucket(From),
    is_bucket(To).

To empty a bucket, we only need to know which one. If we pour water from one to another, we need to describe both. Since we already have a predicate describing a bucket, we can just state a rule as "If From and To are buckets, then pour(From, To) is an action.

Now we need to explain how an action transforms a state. This is a relation between the old state, the new state, and because we'd like to know what happens, also the history.

% initial state
state_goesto_action(buckets(7,5,3), buckets(7,5,3), []).

The transition for the initial state does not change anything and has an empty history (the []).

% state transitions for moving
state_goesto_action(buckets(X,Y,Z), buckets(0,Y,Z), [empty(b1) | History]) :-
    state_goesto_action(_S0, buckets(X,Y,Z), History).

This rule can be read as "If we had an action coming from some state _S0 leading to the state buckets(X,Y,Z) with some History, then we can perform the empty(b1) action next, and we will reach the state buckets(0,Y,Z)". In other words, the state is updated and the action is prepended to the history. A symmetrical rule works for the other buckets:

state_goesto_action(buckets(X,Y,Z), buckets(X,0,Z), [empty(b2) | History]) :-
    state_goesto_action(_S0, buckets(X,Y,Z), History).
state_goesto_action(buckets(X,Y,Z), buckets(X,Y,0), [empty(b3) | History]) :-
    state_goesto_action(_S0, buckets(X,Y,Z), History).

How can we check if this makes sense? Let's look at histories of length 2:

?- state_goesto_action(_,S1,[H1,H2]).
S1 = buckets(0, 3, 5),
H1 = H2, H2 = empty(b1) .

Ah nice, if both actions are empty(b1), the first bucket is empty and the others untouched. Let's look at further solutions:

S1 = buckets(0, 0, 5),
H1 = empty(b1),
H2 = empty(b2) ;

S1 = buckets(0, 3, 0),
H1 = empty(b1),
H2 = empty(b3) ;

S1 = buckets(0, 0, 5),
H1 = empty(b2),
H2 = empty(b1) ;

S1 = buckets(7, 0, 5),
H1 = H2, H2 = empty(b2) ;

S1 = buckets(7, 0, 0),
H1 = empty(b2),
H2 = empty(b3) ;

S1 = buckets(0, 3, 0),
H1 = empty(b3),
H2 = empty(b1) ;

S1 = buckets(7, 0, 0),
H1 = empty(b3),
H2 = empty(b2) ;

S1 = buckets(7, 3, 0),
H1 = H2, H2 = empty(b3).

Looks like we get all possibilities of emptying buckets (and nothing more :-)). Now you need to add rules for pouring from one bucket to the other. Good luck!

(Edit: typos, mistake in the second rule)

lambda.xy.x
  • 4,918
  • 24
  • 35
4

I'm leaving the old answer because it leaves some parts to think about (and the question is about implementing the empty action only). Just to provide a full implementation too:

:- use_module(library(clpfd)).

bucket_capacity(b1,7).
bucket_capacity(b2,3).
bucket_capacity(b3,5).

% projections to a single bucket
state_bucket_value(buckets(X, _, _),b1,X).
state_bucket_value(buckets(_, Y, _),b2,Y).
state_bucket_value(buckets(_, _, Z),b3,Z).

% state update relation by a single bucket
state_updated_bucket_value(buckets(_, Y, Z), buckets(X0, Y,  Z ), b1, X0).
state_updated_bucket_value(buckets(X, _, Z), buckets(X,  Y0, Z ), b2, Y0).
state_updated_bucket_value(buckets(X, Y, _), buckets(X,  Y,  Z0), b3, Z0).


% initial state
state_goesto_action(S0, S0, []) :-
    S0 = buckets(X,Y,Z),
    bucket_capacity(b1,X),
    bucket_capacity(b2,Y),
    bucket_capacity(b3,Z).
% state transition for emptying
state_goesto_action(S1, S2, [empty(Bucket) | History]) :-
    state_updated_bucket_value(S1, S2, Bucket, 0),
    state_goesto_action(_S0, S1, History).
% state transition for pouring
state_goesto_action(S1, S3, [pour(From,To) | History]) :-
    bucket_capacity(b1,Max),
    state_bucket_value(S1,From,X),
    state_bucket_value(S1,To,Y),
    From0 #= min(X+Y, Max),
    To0 #= max(X-Y, 0),
    state_updated_bucket_value(S1, S2, From, From0),
    state_updated_bucket_value(S2, S3, To, To0),
    state_goesto_action(_S0, S1, History).

To find out, if we can find a bucket with exactly one litre, we can fairly enumerate all histories:

?- length(L,_), state_bucket_value(S,_,1), state_goesto_action(_, S, L).
L = [pour(b1, b3), pour(b1, b2), empty(b1), pour(b1, b3)],
S = buckets(5, 0, 1) ;
L = [pour(b1, b3), pour(b1, b2), pour(b1, b1), pour(b1, b3)],
S = buckets(5, 0, 1) ;
L = [pour(b1, b3), pour(b1, b2), pour(b2, b1), empty(b1)],
S = buckets(7, 0, 1) ;
L = [pour(b1, b3), pour(b1, b2), pour(b2, b1), pour(b1, b1)],
[...].

And just to check if the predicate is reversible:

?- L = [pour(b1, b3), pour(b1, b2), empty(b1), pour(b1, b3)], state_goesto_action(_, S, L).
L = [pour(b1, b3), pour(b1, b2), empty(b1), pour(b1, b3)],
S = buckets(5, 0, 1) ;
false.

Edit: Removed domain constraints to speed up computation (we start with a fixed state, so constraints will always be ground and can be propagated without labeling).

lambda.xy.x
  • 4,918
  • 24
  • 35
1

I think the answer would be:

Empty(b,t) => Contains(b,0,t+1)
Azade Farshad
  • 1,022
  • 8
  • 18
  • It makes sense, could you explain your statment ? And which book are you using as reference ? – Mensch Aug 02 '16 at 09:37