OpCoast © 2016

Armed with success for completing the 0-1 knapsack using a branch and bound approach, we now tackle the general form of branch and bound -- and get to a very functional solution. So what about the 0-1 knapsack solver keeps it from being a generalized branch and bound solver? Well firstly, the variable definition includes the Weight and Utility fields which are specific to knapsack. The discrete variables only have states `Unset, Zero`

and `One`

which is fine assuming that the variables are binary. That is a discrete variable can also be one that takes on any value from a set of possibilities (possibly infinite as in 'positive integers'), not just 0 or 1. Also, the branching part of the code makes the same assumption, substituting in `Zero`

or `One`

for `Unset`

variables. This also effected the queue update in that we decided if none, zero or one, or both variable updates should be queued. The function used to evaluate the objective function (that is maximize the knapsack utility, the `multiplySumUtility`

call) was explicitly invoked in the code. And finally the upper bound estimator (`estimator`

) was also explicit in the routine. So there are lot's of things that make this routine specific to the 0-1 knapsack -- but its a good learning experience before trying to create a general solver (assuming our bottom-up approach) -- and it does follow the general method of branch and bound.

Let's start making some changes.

The first change is to make the objective function and estimator functions *parameters* of the branch and bound function. Functions can be arguments since functions are **first-class** objects in F# (F# is not the only language where this is true). We'd like to get to a branch and bound function that starts to look like this, and its invocation:

1: 2: 3: 4: 5: |
let BandB f g vars initLowestValue = (* TBD *) (* invoke *) let (solutionUtility, solutionVars) = BandB fknap gknap vars -1.0 |

That is the objective function (`f`

) and upperbound estimator (`g`

) are to be passed in, assuming that they operate as being applied to `vars`

. We already know the function `f`

here for 0-1 knapsack, its just `multiplySumUtility`

(or is it?), but we don't have `g`

yet. Well, `g`

is `estimator`

with the weight limit for a particular problem built in, we can use partial function application for this. Let's see where this stands so far:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: |
open System.Collections.Generic (* Part II page 1 *) type ZeroOneVarSetting = | Unset (* this is a discriminated union type in F# *) | One | Zero type DiscreteVar = { (* this is a record type in F# *) Name : string; Weight : float; Utility : float; Setting : ZeroOneVarSetting; // discrete value setting } let BandB f g vars initLowestValue = let mutable bestUtility = initLowestValue; let mutable queue = [vars] let mutable bestVars = [| |] // best var settings found, nothing so far. // these are only Informational, not needed in the routine let mutable numberOfQueues = 1 // we just queued the start, so 1 let mutable numberOfEvals = 0 // number of times we evaluated the function with all vars set (* return index of unset (first) var, or -1 if all are set (to One or Zero) *) let unsetVarIndex vars = try Array.findIndex (fun var -> var.Setting = Unset) vars with | :? KeyNotFoundException -> -1 (* active patterns *) let (|TakeBoth|TakeZeroOnly|TakeOneOnly|TakeNeither|) (gZero, gOne, bestSoFar) = if gZero > bestSoFar && gOne > bestSoFar then TakeBoth else if gZero > bestSoFar then TakeZeroOnly else if gOne > bestSoFar then TakeOneOnly else TakeNeither while not queue.IsEmpty do match queue with | curVars :: restOfQueue -> let unsetVarIndex = unsetVarIndex curVars if not (unsetVarIndex = -1) then let varArrayZero = curVars |> Array.mapi (fun index var -> if index = unsetVarIndex then {var with Setting = Zero} else var) let varArrayOne = curVars |> Array.mapi (fun index var -> if index = unsetVarIndex then {var with Setting = One} else var) let gEstimateZero = g varArrayZero let gEstimateOne = g varArrayOne // update the queue queue <- match (gEstimateZero, gEstimateOne, bestUtility) with | TakeBoth -> numberOfQueues <- numberOfQueues + 2 varArrayOne :: varArrayZero :: restOfQueue | TakeZeroOnly -> numberOfQueues <- numberOfQueues + 1 varArrayZero :: restOfQueue | TakeOneOnly -> numberOfQueues <- numberOfQueues + 1 varArrayOne :: restOfQueue | _ -> restOfQueue // Neither case, just take the rest! else let util = f curVars numberOfEvals <- numberOfEvals + 1 if util > bestUtility then (* NOT GOOD AS WE LOST THE WEIGHT LIMIT *) bestUtility <- util bestVars <- curVars queue <- restOfQueue // we've processed curVars, take it off the queue | _ -> () // queue isempty, do nothing printfn "Total settings queued = %d, total number of evals: %d" numberOfQueues numberOfEvals (bestUtility, bestVars) (* Some of this might be Visual Studio specific, you may need to adjust for other platforms *) [<EntryPoint>] let main argv = (* DEFINE vars and functions for 0-1 knapsack *) (* These are deduced to be DiscreteVar record types *) let vars = [|{Name = "food"; Setting = Unset; Weight = 5.0; Utility = 8.0}; {Name = "tent"; Setting = Unset; Weight = 14.5; Utility = 5.0}; {Name = "gps"; Setting = Unset; Weight = 1.0; Utility = 3.0}; {Name = "map"; Setting = Unset; Weight = 0.5; Utility = 3.0} |] let weightLimit = 16.0 let multiplySumEither b v = Array.map (fun vElem -> (if b then vElem.Utility else vElem.Weight) * match vElem.Setting with | One -> 1.0 | _ -> 0.0 ) v |> Array.sum let multiplySumWeight = multiplySumEither false let multiplySumUtility = multiplySumEither true let estimator wlimit vars = // assume vars are sorted in decreasing utility/weight ratio let assignedUtility = multiplySumUtility vars let remainingWeight = wlimit - multiplySumWeight vars let (leftOverWeight,unassignedUtilityEstimate) = Array.fold (fun acc var -> match acc,var.Setting with | (_,accUtil),Zero | (_,accUtil),One | (0.0,accUtil),_ -> (fst acc, accUtil) | (accWt,accUtil),varSetting -> if accWt >= var.Weight then (accWt - var.Weight, accUtil + var.Utility) else let frac = var.Weight / accWt (0.0, accUtil + frac * var.Utility) ) (remainingWeight,0.0) vars assignedUtility + unassignedUtilityEstimate let gknap = estimator weightLimit // this is new let (solutionUtility, solutionVars) = BandB multiplySumUtility gknap vars -1.0 printfn "Best Utility = %f" solutionUtility for var in solutionVars do printfn "%A" var System.Console.ReadKey() |> ignore // don't close the output window until a key is hit 0 |

Notice the appearance of the now-generalized objective function `f`

on line 72 and of the upper bound estimator on lines 55 and 56. These are defined in the invocation at line 125. This is not a bad change, **but we see the issue on line 74**. This line used to only take the improved value for `f`

if the weight of `f`

was within `wlimit`

. But we removed `wlimit`

as an argument baking that into the `g`

function. `wlimit`

is a parameter of the 0-1 knapsack but not of general branch and bound optimization problems so we don't want to add it back to the argument list of `BandB`

, but what if we baked that into `f`

just as we did for `g`

?

This idea will be changed later, but this shows additional usage of partial function application

Well, we don't want `f`

to be acceptable if the weight limit is not met, so what if we re-created a new `f`

that returns -infinity (or the smallest negative number) if the weight limit is not met? So our new portion of the main code would be adapted as:

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: |
let gknap = estimator weightLimit // this is new let knapLimited wlimit vars = let w = multiplySumWeight vars if w <= wlimit then (multiplySumUtility vars) else System.Double.NegativeInfinity let fknap = knapLimited weightLimit let (solutionUtility, solutionVars) = BandB fknap gknap vars -1.0 printfn "Best Utility = %f" solutionUtility for var in solutionVars do printfn "%A" var |

The `knapLimited`

function does just what we thought, and we then create `fknap`

using it with the particular weight limit for our problem instance baked in. However, when we run this we still have a problem ... **recall that the estimator for 0-1 knapsack required the variables in decreasing order of utility/weight ratio**. The user variables as defined are *not* in such an order. We could fix this by sorting the `var`

array before passing it to `BandB`

but this doesn't seem right. The general `BandB`

routine should not require variables in a particular order, although it is perfectly fair that the f and g functions *for a particular problem instance type* would.

1: 2: |
let varsSorted = (Array.sortBy (fun elem -> -elem.Utility / elem.Weight) vars) let (solutionUtility, solutionVars) = BandB fknap gknap varsSorted -1.0 |

Nevertheless, the above will fix the problem, but let's revisit this later; on to page 2