############################################################################
#
#	File:     binpack.icn
#
#	Subject:  Program to demonstrate some bin packing algorithms
#
#	Author:   Gregg M. Townsend
#
#	Date:     June 23, 2000
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  Usage:  binpack [window options]
#
#  Binpack illustrates several approximation algorithms for solving the
#  one-dimensional bin packing problem.
#
#  For references, see the "info" screen.
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  Links: numbers, graphics, random, vsetup
#
############################################################################

link numbers
link graphics
link random
link vsetup

$define Version "Binpack, Version 1.0  (September, 1993)"

$define MAXK 250	# max value of `k' allowed

$define FULL 61261200	# value representing a full bin
			# (least common multiple of {1 to 18, 20, and 25})

$define X0 120		# left edge of bin display
$define DY 165		# vertical spacing
$define YSCALE 155	# scaling for one display

$define BX1 10		# x-coord for first button column
$define BX2 60		# x-coord for second button column
$define BWIDTH 40	# button width
$define BHEIGHT 16	# button height
$define BSPACE 16	# button spacing


# parameter values
global maxsize		# maximum piece size
global nreload		# number of pieces on a reload
global kvalue		# constant `k' used in some algorithms

# current source set
global pieces		# list of piece sizes
global dx		# distance between bins
global bwidth		# bin width
global cdiv		# divisor for converting size to color index

# current output parameters
global bin		# list of current bin sizes
global nfilled		# number of bins (partially) filled
global xll, yll		# lower left corner of display area


# miscellany
global width		# window width
global color		# array of GCs of different colors
global glossary		# list of explanations



#   Future possibilities:
#
#   better layout -- critical controls are too crowded
#   add artificial delays for better visualization
#   implement O(n log n) algs as such instead of O(n^2)
#	n.b. this may not help because can't use Icon's native data structs



#########################  main program  #########################

procedure main(args)
   local v, r, c, gc

   randomize()				# set irreproducible mode

   v := ui(args)			# open window, set up vib-built vidgets
   r := v["root"]
   glossary := []
   addbutton(r, "BF", bestfit, "Best Fit", "picks the fullest possible bin")
   addbutton(r, "WF", worstfit, "Worst Fit", "picks the emptiest bin")
   addbutton(r, "AWF",nearworst,"Almost Worst Fit", "picks second-emptiest bin")
   addbutton(r, "FF", firstfit, "First Fit", "picks the oldest possible bin")
   addbutton(r, "LF", lastfit, "Last Fit", "picks the newest possible bin")
   addbutton(r, "NF", nextfit, "Next Fit", "tries only the current bin")
   addbutton(r, "N(k)", nextk, "Next-k Fit", "tries the k newest bins")
   addbutton(r, "H(k)", harmonic, "Harmonic Algorithm",
      "classifies into {1/1,1/2,...,1/k}")
   addbutton(r, "G(k)", gxfit, "Group-X Fit", "groups into k equal classes")
   VResize(r)

   # workaround freeing of gray highlight color seen with "binpack -Bwhite"
   BevelReset()				# work around color freeing bug

   color := []
   if WAttrib("depth") = 1 then
      put(color, &window)
   else {
      # make a set of colors for different bin heights
      # note that exactly half are reds/yellows and half are blues & darker
      every c := Blend(
            "black", 1, "deep purple-magenta", 10, "cyan-blue",
            1, "reddish-yellow", 11, "orange-red") do {
         gc := Clone(&window)
         Shade(gc, c)
         put(color, gc)
         }
      color := copy(color)		# ensure contiguous
      }

   # keep the following initializations in sync with initial slider positionm
   setmax(v["max"], 20)			# set maximum bin value
   setbins(v["bins"], -100)		# set number of bins
   setk(v["kval"], -10)			# set constant `k' value

   reload()				# initialize random bins
   status("")				# display bin count

   &error := 1
   WAttrib("resize=on")
   &error := 0

   r.V.event := 1			# disable screen erase on resize
   GetEvents(r, leftover)		# enter event loop
end


#  addbutton -- add a button (and a D variant) on every shelf

procedure addbutton(r, label, proc, name, defn)
   local v, n, y
   static yoff
   initial yoff := 0

   y := yoff +:= BSPACE
   while (y +:= DY) < WAttrib("height") do {
      Vbutton(r, BX1, y, r.win, label, pack, proc, V_RECT, BWIDTH, BHEIGHT)
      Vbutton(r, BX2, y, r.win, label||"D", pack, proc, V_RECT, BWIDTH, BHEIGHT)
      }
   put(glossary, left(label, 6) || left(name, 20) || defn)
   return
end



#########################  parameter setting  #########################

#  These routines are called during initialization and in response to
#  slider movement.


#  setk(v, n) -- set value of constant `k', based on 1 - 100 slider scale

procedure setk(v, n)
   if n >= 0 then				# if slider call
      n := integer(MAXK ^ ((n / 100.0) ^ 0.70))	# convert nonlinearly
   else
      n := -n					# initial call
   kvalue := roundoff(n)
   GotoXY(v.ax, v.ay + v.ah + 14)
   WWrites(left("k=" || kvalue, 8))
   return
end


#  setmax(v, n) -- set maxsize, based on 1 - 20 slider scale.

procedure setmax(v, n)
   local fract

   fract := n / 20.0
   maxsize := integer(fract * FULL)
   GotoXY(v.ax, v.ay + v.ah + 14)
   WWrites(" max size ", ((fract || "00") ? move(4)))
   return
end


#  setbins(v, n) -- set number of bins, based on 1 - 100 slider scale

procedure setbins(v, n)
   local s, max

   max := WAttrib("width") - 40 - X0		# max that will fit on screen
   if &shift then				# allow more if shifted
      max /:= 1.1 * (maxsize / (2.0 * FULL))

   if n >= 0 then				# if slider call
      n := integer(max ^ ((n / 100.0) ^ 0.40))	# convert nonlinearly
   else
      n := -n					# initial call
   n <:= 5
   n := roundoff(n, 5)				# convert to round number

   nreload := n
   s := center(nreload, 5)
   GotoXY(v.ax + (v.aw - TextWidth(s)) / 2, v.ay + v.ah + 17)
   WWrites(s)
   return
end


#  roundoff(n) -- truncate n to a nice number divisible by m (at least)

procedure roundoff(n, m)
   local d

   if n > 1000 then {
      if n > 10000 then
         d := 1000
      else if n > 5000 then
         d := 500
      else
         d := 100
      }
   else if n > 500 then
      d := 50
   else if n > 100 then
      d := 10
   else if n > 50 then
      d := 5
   n -:= n % \d
   n -:= n % \m
   return n
end


#########################  bin packing primitives  #########################


#  empty(n) -- empty shelf n

procedure empty(n)
   bin := list(*pieces, 0)
   nfilled := 0
   xll := X0
   yll := n * DY
   EraseArea(xll, yll - DY + 1, , DY)
   width := WAttrib("width")
   return
end


#  place(p, b) -- add a piece of size p to bin b

procedure place(p, b)
   local o, t, x, y0, y1
   static invfull
   initial invfull := 1.0 / FULL

   o := bin[b] | fail
   if (t := o + p) > FULL then
      fail
   bin[b] := t
   nfilled <:= b
   if (x := xll + (b - 1) * dx) < width then {
      y0 := integer(yll - YSCALE * o * invfull)
      y1 := integer(yll - YSCALE * t * invfull) + 1
      FillRectangle(color[p / cdiv + 1], x, y1, bwidth, 0 < (y0 - y1))
      }
   return
end


#  status(s) -- write string s and shelf population at end of output shelf

procedure status(s)
   local x

   x := xll + nfilled * dx + 4
   x >:= width - 40
   GotoXY(x, yll - 15)
   WWrites(s)
   GotoXY(x, yll)
   WWrites(nfilled)
   return
end



#########################  source set manipulation  #########################


#  reload() -- reload first shelf with random-sized pieces.

procedure reload()
   local i, j, z, p

   pieces := list(nreload)
   empty(1)
   dx := (width - 40 - X0) / nreload
   dx <:= 1
   dx >:= 20
   bwidth := 4 * dx / 5
   bwidth <:= 1
   cdiv := (maxsize + *color - 1) / *color
   every place(pieces[i := 1 to *pieces] := ?maxsize, i)
   status("new")
   return
end


#  mix() -- randomly reorder the first shelf.
#
#  if shifted, place equally-spaced using golden ratio

procedure mix()
   local i, n, p

   if &shift then {
      n := integer(*pieces / &phi + 1)
      while gcd(*pieces, n) > 1 do
         n -:= 1
      i := 0
      every p := !sort(pieces) do {
         i := (i + n) % *pieces
         pieces[i + 1] := p
         }
      }
   else
      every i := *pieces to 2 by -1 do
         pieces[?i] :=: pieces[i]

   empty(1)
   every place(pieces[i := 1 to *pieces], i)
   status("mix")
   return
end



#  order() -- sort the first shelf in descending order
#
#  if shifted, sort ascending

procedure order()
   local i

   pieces := sort(pieces)
   if not &shift then
      every i := 1 to *pieces / 2 do	# change from ascending to descending
         pieces[i] :=: pieces[-i]

   empty(1)
   every place(pieces[i := 1 to *pieces], i)
   status("sort")
   return
end



#########################  packing algorithms  #########################



#  pack(x, v) -- execute packing algorithm connected with button x

procedure pack(x, v)
   local l, n, s, i

   if x.ax = BX2 then {
      l := sort(pieces)			# if second-column button, sort first
      every i := 1 to *l/2 do		# change from ascending to descending
         l[i] :=: l[-i]
      }
   else
      l := copy(pieces)

   n := x.ay / DY + 1			# compute shelf number
   empty(n)				# clear the shelf

   s := x.id(l)				# call packing algorithm
   status(\s | x.s)			# display status
   return
end


#  nextfit(l) -- pack using next-fit algorithm

procedure nextfit(l)
   local p

   every p := !l do
      place(p, nfilled | nfilled + 1)
   return
end


#  nextk(l) -- pack using next-k-fit algorithm

procedure nextk(l)
   local p

   every p := !l do
      if nfilled <= kvalue then
         place(p, 1 to nfilled + 1)
      else
         place(p, nfilled - kvalue + 1 to nfilled + 1)
   return "N" || kvalue
end


#  firstfit(l) -- pack using first-fit algorithm

procedure firstfit(l)
   local p

   every p := !l do
      place(p, 1 to nfilled + 1)
   return
end


#  lastfit(l) -- pack using last-fit algorithm

procedure lastfit(l)
   local p

   every p := !l do
      place(p, (nfilled to 1 by -1) | (nfilled + 1))
   return
end


#  bestfit(l) -- pack using best-fit algorithm

procedure bestfit(l)
   local p, b, i, max, found

   every p := !l do {
      max := FULL - p			# fullest acceptable bin size
      found := 0			# size of best bin found so far
      b := nfilled + 1			# index of where found
      every i := 1 to nfilled do
         if found <:= (max >= bin[i]) then
            b := i
      place(p, b)			# place in best bin found
      }
   return
end


#  worstfit(l, n) -- pack using worst-fit algorithm

procedure worstfit(l, n)
   local p, b, i, found

   every p := !l do {
      found := FULL - p			# size of best bin found so far
      b := nfilled + 1			# index of where found
      every i := 1 to nfilled do
         if found >:= bin[i] then
            b := i
      place(p, b)			# place in best bin found
      }
   return
end


#  nearworst(l, n) -- pack using almost-worst-fit algorithm

procedure nearworst(l, n)
   local p, a, b, i, found

   every p := !l do {
      found := FULL - p			# size of best bin found so far
      a := b := &null
      every i := 1 to nfilled do
         if found >:= bin[i] then {
            a := b
            b := i
            }
      place(p, \a | \b | (nfilled + 1))	# place in second-best bin found
      }
   return
end


#  harmonic(l, n) -- pack using (unmodified) harmonic algorithm

procedure harmonic(l, n)
   local curr, maxv, i, p, b

   curr := list(kvalue)			# current bin for each class
   maxv := list(kvalue)			# maximum for each class
   every i := 1 to kvalue do
      maxv[i] := FULL / (kvalue - i + 1)

   every p := !l do {
      p <= maxv[i := 1 to kvalue]	# find class index i
      b := curr[i]
      if /b | (bin[b] + p > FULL) then
         place(p, curr[i] := nfilled + 1)
      else
         place(p, b)
      }
   return "H" || kvalue
end


#  gxfit(l, n) -- pack using group-x(k)-fit algorithm

procedure gxfit(l, n)
   local stk, maxv, i, s, p, b, d

   stk := []				# stacks of bins, one for each group
   maxv := []				# maximum for each group

   # make k equally sized groups
   d := FULL / kvalue
   every i := 1 to kvalue do {
      put(stk, [])
      put(maxv, i * d - 1)
      }

   every p := !l do {
      # find group index i for piece
      (p <= maxv[i := (1 to kvalue) | 0]) & (*stk[i] > 0)
      b := pop(stk[i]) | (nfilled + 1)
      place(p, b)
      # now put bin back on a stack, if not too full
      if (FULL - bin[b]) >= maxv[i := (kvalue - 1 to 1 by -1)] then
         push(stk[i], b)
      }
   return "G" || kvalue
end



#########################  event miscellany  #########################



#===<<vib:begin>>===	modify using vib; do not remove this marker line
procedure ui(win, cbk)
return vsetup(win, cbk,
   [":Sizer:lucidasanstypewriter-bold-12::0,0,860,675:Bin Packing",],
   ["bins:Slider:h::10,48,100,15:0,100,40",setbins],
   ["infob:Button:regular::10,111,40,17:info",info],
   ["kval:Slider:h::10,135,100,15:0,100,30",setk],
   ["max:Slider:h::10,10,100,15:1,20,20",setmax],
   ["mix:Button:regular::10,68,30,17:mix",mix],
   ["new:Button:regular::80,68,30,17:new",reload],
   ["quit:Button:regular::70,110,40,17:quit",quit],
   ["sort:Button:regular::10,87,35,17:sort",order],
   )
end
#===<<vib:end>>===	end of section maintained by vib



#  leftover() -- handle events that fall outside the vidgets
#
#  Exits when certain keys are pressed and ignores other events.

procedure leftover(e)
   case e of {
      QuitEvents():	exit()
      &meta & !"nN":	reload()
      &meta & !"mM":	mix()
      &meta & !"sS":	order()
      &meta & !"iI":	info()
      }
   return
end


#  quit() -- handle "quit" button press

procedure quit(x, v)
   exit()
end


#  info() -- handle "info" button press

procedure info(x, v)
   static text
   initial {
      text := ["",
         Version,
         "by Gregg Townsend, The University of Arizona",
         "",
         "",
         "Glossary:",
         ""]
      every put(text, "   " || !glossary)
      put(text,
         "",
         "A `D' suffix indicates a variation where the input is sorted",
         "in descending order before applying the algorithm.",
         "",
         "",
         "For more information about bin packing algorithms, see:",
         "",
         "   `Approximation Algorithms for Bin-Packing -- An Updated Survey'",
         "   by E.G. Coffman, Jr., M.R. Garey, and D.S. Johnson, in",
         "   Algorithm Design for Computer System Design, ed. by",
         "   Ausiello, Lucertini, and Serafini, Springer-Verlag, 1984",
         "",
         "   `Fast Algorithms for Bin Packing' by David S. Johnson,",
         "   Journal of Computer and System Sciences 8, 272-314 (1974)",
         "")
      }
   Notice ! text
   return
end
