In the movie Die Hard with a Vengeance where Bruce Willis and Samuel L. Jackson have to defuse a bomb by placing a 4 gallon jug of water on a set of scales. They only have a 3 gallon jug and a 5 gallon jug. Both of them don't have markings. They must use two jugs to get precise 4 gallon jug of water before the bomb go off.
How do we solve this challenge with Cyclone ?
In a simple sentence, Cyclone can solve this problem in a pretty neat way. We just need to model different states of actions. There is a list of possible 6 actions for Bruce Willis and Samuel L. Jackson can perform:
normal state EmptyContainer1{
container1=0;
}
normal state FillContainer1{
container1=3;
}
normal state EmptyContainer2{
container2=0;
}
normal state FillContainer2{
container2=5;
}
int container1 where container1>=0 && container1<=3;
int container2 where container2>=0 && container2<=5;
The tricky part here is how to model action 1 and 2: pour water from 3 gallon jug into 5 gallon jug and vice versa. Let's analyze action 1:
In order for 5 gallon jug to receive some water from 3 gallon jug, we must ensure 5 gallon jug is not full. In other words, we have to prevent the jug from overflow.
On the other hand, if we cannot fill 5 gallon jug with amount of the water in 3 gallon jug, then we are ensured that the jug is not overflown. Hence, there are two cases here:
// Pour water from 3 gallon jug to 5 gallon jug
normal state Con1ToCon2{
(container1 + container2 > 5) => (amount == 5 - container2);
(container1 + container2 <= 5) => (amount == container1);
}
Here, the operator => means implication. The variable amount denotes the amount of water can be poured from 3 gallon jug. Implication (=>) in Cyclone only works with boolean conditions, and this forces Cyclone compiler to compute the correct amount of water in order to make conditions hold.
It is obvious that the amount of water can be poured must stay between 0 and 3 (decided by the smaller gallon jug.). Thus, we can also constrain our variable amount in the following way:
// amount of water can pour from one jug to the other
int amount where amount>=0 && amount<=3;
Since the amount of water poured to a jug could be different every time for action 1 and 2, there is no need to keep track this number as long as both of jugs do not overflow. To do this, we use a fresh statement. Hence, our final specification for action 1 is as follows:
// Pour water from 3 gallon jug to 5 gallon jug
normal state Con1ToCon2{
fresh(amount);
(container1 + container2 > 5) => (amount == 5 - container2);
(container1 + container2 <= 5) => (amount == container1);
container1 = container1 - amount;
container2 = container2 + amount;
}
The fresh statement asks Cyclone compiler to create a new fresh variable every time a node/state is visited in a path (to be discovered), and we do not need to keep track of its value. This fresh variable retains amount's type and condition imposed by where clause. In short, you can think this fresh variable as a copy of amount but may have a different value every time the state Con1ToCon2 gets visited. Hence, we are now guaranteed that every time action 1 is performed, a different amount of water can be poured.
We can write a similar specification for action 2 (pour water from 5 gallon jug to 3 gallon jug).
// Pour water from 5 gallon jug to 3 gallon jug
normal state Con2ToCon1{
fresh(amount);
(container1 + container2 > 3) => (amount == 3 - container1);
(container1 + container2 <= 3) => (amount == container2);
container1 = container1 - amount;
container2 = container2 + amount;
}
At the beginning, both jugs are empty.
start normal state Start{
container1 = 0;
container2 = 0;
}
Next, we build the connections among all possible actions.
trans t1 { Start -> Con2Con1 }
trans t2 { Start -> Con1Con2 }
trans t3 { Start -> FillContainer1 }
trans t4 { Start -> FillContainer2 }
trans t5 { Start -> EmptyContainer1 }
trans t6 { Start -> EmptyContainer2 }
...
...
...
trans t36 { FillContainer2 -> FillContainer1 }
Finally, we write our goal section to ask Cyclone find a path that can make our 5 gallon jug containing precise 4 gallon water.
goal {
assert (container2==4);
check for 2,3,4,5,6
reach (Con1ToCon2,Con2ToCon1,EmptyContainer1,
EmptyContainer2,FillContainer1,FillContainer2)
}
Start->FillContainer2->Con2ToCon1->EmptyContainer1->Con2ToCon1->FillContainer2->Con2ToCon1
The complete specification can be downloaded here. In fact, 6 here is the minimum number of actions required. However, can you use Cyclone to find all other solutions?
Here is the video:
@2020-2022 Hao Wu. All rights reserved. Last update: January 29, 2022