The Wolf, goat, and cabbage problem can be stated as [1]:
A farmer with a wolf, a goat, and a cabbage must cross a river by boat. The boat can carry only the farmer and a single item. If left unattended together, the wolf would eat the goat, or the goat would eat the cabbage. How can they cross the river without anything being eaten?
A long transcontinental flight was a good opportunity to try to attack this problem. Here are some approaches to model this. Not at all a very useful or practical model, but still interesting, I think (although I may be in a small minority here).
Inventory model
In this model, we keep track of the inventory of items (wolf, goat, cabbage). We assume the starting inventory is: all items are on the left bank of the river. The final inventory should be: all items are on the right bank. In this model, I assume the following numbering scheme:
---- 31 SET trip trips trip1 , trip2 , trip3 , trip4 , trip5 , trip6 , trip7 , trip8 , trip9 , trip10 ---- 31 SET dir direction of trip L->R, R->L ---- 31 SET tripDir trip direction combos L->R R->L trip1 YES trip2 YES trip3 YES trip4 YES trip5 YES trip6 YES trip7 YES trip8 YES trip9 YES trip10 YES
We don't know in advance how many trips we need. In a MIP model, however, we need to statically size the problem in advance. We hope 10 trips is more than enough. If it is too small, the model will be infeasible. If too large, the MIP model is larger and more difficult to solve than needed.
Let's first model the passengers on each trip. We have 0 or 1 of them.
paxtrip,item∈{0,1}item∈{wolf,goat,cabbage}
The capacity constraint is obvious:
∑itempaxtrip,item≤1∀trip
The inventory is handled using an inventory balance equation, something we often see in production scheduling models:
invtrip,side,item∈{0,1}side∈{L,R}invtrip,side,item=invtrip−1,side,item+paxtrip,item⋅Iarrival(trip,side)−paxtrip,item⋅Ideparture(trip,side)+initialInvitem,trip
The initialInvitem,trip has only non-zero values for the first trip. It establishes the initial inventory (all items on the left bank). The notation Iarrival(trip,side) is an indicator function: Iarrival(trip,side)={1if this is an arrival0otherwise Similar for the departures. Note that these indicator functions are defined over set elements, which are completely exogenous. We can precompute this easily, forming some useful data structure. In the GAMS model, I used a 2d set for this.
To forbid leaving certain combinations without supervision (the cases are: goat/cabbage, wolf/goat), we can use some simple linear constraints:∑itemeatcase,item⋅invtrip,side,item≤1∀case,trip,side This is a bit too strict: we only want this check for departures, and not for arrivals. Also, we may want to relax this constraint when we are done (when all items have arrived on the right bank). So this becomes:
∑itemeatcase,item⋅invtrip,side,item≤1+donetrip∀case,trip,side|Ideparture(trip,side)=1
The donetrip∈{0,1} variable indicates when we are done. We could say donetrip=invtrip,R,wolf⋅invtrip,R,goat⋅invtrip,R,cabbage We don't have to add:donetrip=invtrip,R,wolf⋅invtrip,R,goat⋅invtrip,R,cabbage⋅(1−invtrip,L,wolf)⋅(1−invtrip,L,goat)⋅(1−invtrip,L,cabbage) as the inventory balance takes care of the inventory at the left bank. The binary multiplication (this is nonlinear and nonconvex) can be easily linearized as: y=x1⋅x2⋅x3⟺{y≤x1y≤x2y≤x3y≥x1+x2+x3−2
We can enforce the donetrip to remain 1 once we are done by: donetrip≥donetrip−1 This constraint is not really needed (the objective takes care of this), but it is conveying what we know about optimal solutions. It can also help with performance (not really an issue here). I prefer to keep it in the model.
The objective I used is: maximize the number of "done" trips: max∑tripdonetrip This will generate a tight schedule (no unnecessary trips).
The complete model is:
Inventory based model |
---|
max∑tripdonetrip∑itempaxtrip,item≤1∀tripinvtrip,side,item=invtrip−1,side,item+paxtrip,item⋅Iarrival(trip,side)−paxtrip,item⋅Ideparture(trip,side)+initialInvitem,trip∀trip,side,item∑itemeatcase,item⋅invtrip,side,item≤1+donetrip∀case,trip,side|Ideparture(trip,side)=1donetrip≤invtrip,R,item∀trip,itemdonetrip≥∑iteminvtrip,R,item−2∀tripdonetrip≥donetrip−1∀trip>1donetrip∈{0,1}paxtrip,item∈{0,1}invtrip,side,item∈{0,1} |
The results of the model are:
---- 116 VARIABLE pax.L passengers: items taken on each trip wolf goat cabbage trip1 1 trip3 1 trip4 1 trip5 1 trip7 1 ---- 116 VARIABLE inv.L inventory just after trip L.wolf L.goat L.cabbage R.wolf R.goat R.cabbage trip1 1 1 1 trip2 1 1 1 trip3 1 1 1 trip4 1 1 1 trip5 1 1 1 trip6 1 1 1 trip7 1 1 1 trip8 1 1 1 trip9 1 1 1 trip10 1 1 1 ---- 116 VARIABLE done.L if 1 we are done trip7 1, trip8 1, trip9 1, trip10 1 ---- 116 VARIABLE z.L = 4.000 objective
---- 132 PARAMETER trace trip report L.wolf L.goat L.cabbage R.wolf R.goat R.cabbage initial. . 1 1 1 trip1 .L->R.goat 1 1 1 trip2 .R->L. 1 1 1 trip3 .L->R.cabbage 1 1 1 trip4 .R->L.goat 1 1 1 trip5 .L->R.wolf 1 1 1 trip6 .R->L. 1 1 1 trip7 .L->R.goat 1 1 1
On the left we see the passengers taken along each trip. On the right we have the inventory just after each trip. Only after trip 7, we are done.
This is not the only solution. Basically, we can reverse the schedule of the wolf and the cabbage. A no-good cut can be added to the model to forbid a previous solution (but allow all other solutions). For an optimal binary solution x∗i, such a cut looks like ∑i|x∗i=1xi−∑i|x∗i=0xi≤∑i|x∗i=11−1 In our case, we can apply this to just the variable paxtrip,item. The variable inv is completely determined once we know pax, so we can ignore it here.
If we add this no-good cut, we indeed see there are two different passenger schedules:
---- 116 VARIABLE pax.L passengers: items taken on each trip wolf goat cabbage trip1 1 trip3 1 trip4 1 trip5 1 trip7 1 |
---- 160 VARIABLE pax.L passengers: items taken on each trip wolf goat cabbage trip1 1 trip3 1 trip4 1 trip5 1 trip7 1 |
All in all, this problem was not super easy to model. Of course, that allows us to discuss quite a few formulation tricks for a rather simple problem.
GAMS Model
Shortest path formulation
- The location of the node (left or right). We can also interpret this as where the boat (or the farmer) is at this point in time.
- The inventory. I store both the left and right inventory, but we could save a bit of space by just storing one of them.
---- 52 SET state state: node location + inventory
L.wolf L.goat L.cabbage R.wolf R.goat R.cabbage
node1 .L YES YES YES
node2 .L YES YES YES
node3 .L YES YES YES
node4 .L YES YES YES
node5 .L YES YES YES
node6 .L YES YES YES
node7 .L YES YES YES
node8 .L YES YES YES
node9 .R YES YES YES
node10.R YES YES YES
node11.R YES YES YES
node12.R YES YES YES
node13.R YES YES YES
node14.R YES YES YES
node15.R YES YES YES
node16.R YES YES YES
Not all nodes are allowed. E.g. node 9 is illegal: the farmer is on the right bank, so we can't have wolf+goat or goat+cabbage on the left. When we check all nodes, we are only left with the following list:
---- 83 SET rnode reduced node set
node1 , node2 , node3 , node5 , node6 , node11, node12, node14, node15, node16
The set of arcs is not the cartesian product of the nodes. We can only ship 0 or 1 item. So, we have a somewhat sparse network. Here is what I work with:
---- 133 SET pax passengers along arc
wolf goat cabbage empty
node1 .node11 YES
node2 .node12 YES
node2 .node14 YES
node3 .node11 YES
node3 .node12 YES
node3 .node15 YES
node5 .node14 YES
node5 .node15 YES
node6 .node14 YES
node6 .node16 YES
node11.node1 YES
node11.node3 YES
node12.node2 YES
node12.node3 YES
node14.node2 YES
node14.node5 YES
node14.node6 YES
node15.node3 YES
node15.node5 YES
node16.node6 YES
I use an LP model to solve the shortest path problem:
Shortest path LP Model |
---|
min∑(i,j)∈Afi,j∑j|(j,i)∈Afj,i+supplyi=∑j|(i,j)∈Afi,j+demandi∀ifi,j≥0supplyi={1if i is the source node0otherwisedemandi={1if i is the sink node0otherwise |
The raw results are:
---- 160 VARIABLE f.L flow node2 node3 node6 node11 node12 node14 node16 node1 1.000 node2 1.000 node3 1.000 node6 1.000 node11 1.000 node12 1.000 node14 1.000
This is a bit difficult to interpret. With a bit of postprocessing, we can show:
---- 188 SET trace results from LP model
L.wolf L.goat L.cabbage R.wolf R.goat R.cabbage
initial.node1 . . YES YES YES
trip1 .node1 .node11.goat YES YES YES
trip2 .node11.node3 . YES YES YES
trip3 .node3 .node12.cabbage YES YES YES
trip4 .node12.node2 .goat YES YES YES
trip5 .node2 .node14.wolf YES YES YES
trip6 .node14.node6 . YES YES YES
trip7 .node6 .node16.goat YES YES YES
The part on the right is the inventory immediately after a trip (i.e. traveling along the arc). As before, we need 7 trips to move all items from the left bank to the right bank.
The LP model itself is trivial and standard, but designing and populating the network was again not super easy. Compared to the first model, we moved complexity from the model equations to data manipulation.
GAMS Model
References
- Wolf, goat, and cabbage problem, https://en.wikipedia.org/wiki/Wolf,_goat_and_cabbage_problem
No comments:
Post a Comment