An example on the level of a test after the end of semester
We have recently moved into a new apartment, and we are in trouble of putting our books on the shelves in an organized way. The shelf system that we have is an adjustable one. There are 7 shelf plates, whose height we can decide. For the sake of this example, we can assume, that not only fixed positions are available, but anything above 50 cm. The plates are 120 cm wide, 20 cm deep and 2 cm thick.
Our books are organized into booksets, like math books, IT books, Tolkien novels, Harry Potter series, diplomas of formerly supervised students, etc. For each bookset we know the total width of the books in that bookset, and the height of the tallest among them.
Our gool is to put the sets on the shelves in such a way, that the highest we must reach for anything is minimal. To keep order, we don't want to split up any sets, and we also want to have a 7cm gap between the highest book on a shelf and the shelf above. We are, however, not forced to use all of the plates, if it is not necessary.
The raw data for the problem is given below:
set BookSets := Math IT Sport Music Novel Tolkien Feist HarryPotter Diplom; param : width height:= Math 100 20 IT 80 25 Sport 30 30 Music 50 30 Novel 40 20 Tolkien 30 25 Feist 50 20 HarryPotter 90 25 Diplom 70 40 ; param shelfCount := 7; param shelfWidth := 120; param shelfHeight := 2; param minDistance := 7; param minPosition := 50;
To be able to validate your model, here is an optimal arrangement of books. Obviously, many permutations will have the same result. It is easy to see, that if shelves below the top one are exchanged, than the objective will not change.
Try to figure out the model yourself. A tip: use redundant variables for the positioning of the shelves, and for the height of the tallest book on a shelf. Reveal solution
# Input data declaration
set BookSets;
param width{BookSets};
param height{BookSets};
param shelfCount;
set Shelves:=1..shelfCount;
param shelfWidth;
param shelfHeight;
param minDistance;
param minPosition;
param M:=500;
# Variables
var use{Shelves} binary;
var position{Shelves} >= minPosition;
var put{BookSets,Shelves} binary;
var maxbookheight{Shelves}>=0;
var maxusedshelf;
# Constraints
# If a shelf is used all the shelves below it are used
s.t. ShelfUsage {s in Shelves : s!=1}:
use[s] <= use[s-1];
# If a shelf is used, it should be higher then the one below it (maxbook + 7cm)
s.t. MaxBookHeightSetter{b in BookSets, s in Shelves}:
maxbookheight[s] >= height[b] * put[b,s];
s.t. ShelfPositioning{s in Shelves: s!=1}:
position[s] >= position[s-1]+maxbookheight[s-1]+shelfHeight+minDistance;
# Only put booksets on used shelves
# Only put as many booksets on shelves as they can hold
s.t. ShelfCapacity{s in Shelves}:
sum{b in BookSets} put[b,s] * width[b] <= shelfWidth * use[s];
# Each booksets must be put on exactly one shelf
s.t. BookSetAssignment{b in BookSets}:
sum{s in Shelves} put[b,s] = 1;
# Set max used shelf
s.t. MaxUsedShelf{s in Shelves}:
maxusedshelf >= position[s] - M *(1 - use[s]);
# Objective function
minimize topShelfHeight: maxusedshelf;
# Display statements
solve;
printf "<svg width='%d' height='%d'>",shelfWidth+20,(sum{ss in Shelves: (ss==shelfCount && use[ss]==1) || (use[ss]==1 && use[ss+1]==0)} (position[ss] + maxbookheight[ss]+shelfHeight)-position[1])+20; # <-- s/500/totalheight
for {s in Shelves : use[s]==1}
{
printf "<rect x='%d' y='%d' width='%d' height='%d' style='fill:rgb(214, 119, 36)' />",10,10+sum{ss in Shelves: (ss==shelfCount && use[ss]==1) || (use[ss]==1 && use[ss+1]==0)} (position[ss] + maxbookheight[ss]+shelfHeight)-position[s]-shelfHeight,shelfWidth,shelfHeight; # <-- s/500/totalheight
for {b in BookSets : put[b,s]==1}
{
printf "<rect x='%d' y='%d' width='%d' height='%d'"
,10+sum{b2 in BookSets : b2<b && put[b2,s]}width[b2]
,10+sum{ss in Shelves: (ss==shelfCount && use[ss]==1) || (use[ss]==1 && use[ss+1]==0)} (position[ss] + maxbookheight[ss]+shelfHeight)-(position[s]+shelfHeight)-height[b] # <-- s/500/totalheight
,width[b]
,height[b]
;
printf " style='fill:rgb(%d,%d,80);stroke:black;stroke-width:1' />"
,(1-height[b]/maxbookheight[s])*100+155
,50+200*width[b]/shelfWidth
;
printf "<text x='%d' y='%d' font-family='Verdana' font-size='6' fill='white'>%s</text>"
,15+sum{b2 in BookSets : b2<b && put[b2,s]}width[b2]
,23+sum{ss in Shelves: (ss==shelfCount && use[ss]==1) || (use[ss]==1 && use[ss+1]==0)} (position[ss] + maxbookheight[ss]+shelfHeight)-(position[s]+shelfHeight)-height[b] # <-- s/500/totalheight
,b
;
}
}
printf "</svg>";
Observe the display statements at the end of the code, they generate the svg figure shown above.
Imagine, that we have a given frequency for all of the booksets, that means, how often we get one of the books from it let's say in a year. The most comfortable height for us to fetch something is 120 cm, anything below or above that requires extra "effort", that is linear to the difference from 120 cm. Organize the books in such a way, that our overall effort over the year is minimal.