Learning F#: Case study with branch and bound – PART I

OpCoast © 2016

# Part I: 0-1 Knapsack

Before delving into the more general branch and bound algorithm in F#, let’s try one to implement the simpler **0-1 Knapsack problem** using branch and bound techniques. This problem mimics the idea of a person trying to pack a limited weight (or space) bag (knapsack) with items such that their total value (utility) is maximized. Mathematically, it is formulated as:

0-1 Knapsack definition

Here, there are variables each denoted as * _{}*. Likewise there are weights associated with each item and utility (value) settings for each, denoted

*and*

_{}*respectively. Note that the terminology used here differs from the Wiki link above in that variable, weight and utility indices go from 0 to -1 to be more in line with computer array indexing and the value is denoted with instead of*

_{}0-1 Knapsack has a pseudo-polynomial solution approach, but here we are focused on applying a branch and bound approach. Later we will look at other problem types.

## Short Description of Branch and Bound

Before getting back to the case of the knapsack problem it will help to understand how branch and bound works. Consider the following *search tree* diagram:

Branch and Bound Illustration

Deciding on the selection of each variable can be viewed as a tree structure, where each branch is a variable selection and each node represents the setting (or non-setting) of each variable. At the top of the tree, all of the variables are unset. In the drawing, we have a case of three variables (indexed from 0 to 2). At each level of the tree one variable is decided (set). As an example, if you follow the red line from the top to the bottom, the variables are set to * _{}* = 0,

*= 1, and*

_{}*= 1. At the bottom level, all of the variables are set and therefore a value of the knapsack and a weight of the knapsack can be computed (see Eq 1). At any intermediate node other than the bottom layer, at least one variable remains to be set. There are 2*

_{}^{}nodes at the bottom layer, that is all the combinations of variables, in the figure =3 so there are 8 in that case. And there are 2

^{n+1}-1 nodes in the whole graph for any variables.

Notice that the nodes at the bottom are labeled with ‘U’ and the rest as ‘G’. This is meant to imply that nodes with complete settings have a particular utility while the rest (the ‘G’s) have only estimates; we will later be using a function named ` hence the G moniker, but that is getting ahead of ourselves.`

## Programming

A first step is deciding how to represent the problem domain in code or the type system. We see that we have three arrays to represent, each with a cardinality of . The weights and utilities could be either integers or floating point values, let’s choose the more inclusive floating point (`float`

in F#). The variables of our domain can be either zero or one, or in the case of branch and bound **unset**. This leads us to determine how to represent (the type) the variables of our problem. We have decided to type the variables as `DiscreteVar`

1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
(* this is setting for a variable of the problem *)type ZeroOneVarSetting = | Unset (* this is a discriminated union type in F# *) | One | Zero(* this is a variable of the problem. Each var has a Name and mutable v*)type DiscreteVar = { (* this is a record type in F# *) Name : string; mutable Setting : ZeroOneVarSetting; // discrete value setting } |

I assume that you know the basics of F#

We used a *discriminated union* type to capture the possible settings for a variable as either Unset, Zero or One, makes sense. Then, the `DiscreteVar`

is a *record type* with a Name and Settings that use this type. To define a problem instance, we might use something like:

Arrays are denoted with `[| |]`

while lists are denoted as `[ ]`

, each with elements separated with semicolons.

1: 2: 3: 4: 5: 6: 7: |
let vars = [|{Name = "food"; Setting = Unset}; {Name = "tent"; Setting = Unset}; {Name = "gps"; Setting = Unset }; {Name = "map"; Setting = Unset} |] let weights = [|5.0; 14.5; 1.0; 0.5 |] let utilities = [| 8.0; 5.0; 3.0; 3.0 |] let weightLimit = 20.0 |

Notice that each of vars, weights and utilities are *array* types. Another choice might be to make them *lists*. F# arrays allow for modification of an element, while lists do not. If we want to change an element of a list we need to extract the elements leading up to the element to be changed, concatenate this with the new element and then concatenate the rest and assign this to a new variable (see topic). Array sizes are fixed, but that works since once we define a problem set (as above) the size of these arrays doesn’t need to change. We make the setting for the variable *mutable* since we think we need to change it later. These are our **ideas at this point** anyway.

The approach throughout is to start with something and refine it as we go along.

Notice that we never defined the type of element that `vars`

contains. F# **deduces** that the type of element is a record type and furthermore that the type is `DiscreteVar`

based on seeing that these record names match in these record expressions (see topic). OK, let’s write a function to determine the total utility.

1: 2: 3: 4: 5: 6: |
let utility u v = let products = Array.map2 (fun uElem vElem -> uElem * vElem) u v Array.sum products let tv = utility utilities vars |

Line 1 defines a function named `utility`

which takes two arguments named ` and `

`. At this point we haven’t declared what types `

` and `

` are, and the compiler deduces their type from the body of the function `

**and** from subsequent uses. In fact, if you put that code into F#, the utility definition is fine *until* you add the invocation at line 5. Before adding line 5, the compiler thinks of the function as:

Type Deduction

That is, it knows that ` and `

` are arrays, given their appearance as arguments to the `

`Array.map2`

function; it also knows that the array elements must allow the `(*)`

operator so it chooses `int`

as the array element type sans other information. Recall that `Array.map2`

takes as arguments a function that accepts two arguments and produces a value; the two arrays which must be of the same length. However, when we add the line 5 content, an error arises:

Type Mis-Deduction

Now, the compiler sees that the ` argument is a float array, and then sees that it cannot apply the `

`(*)`

operator to a float and a `DiscreteVar`

(since ` is a `

`DiscreteVar`

array in this instance). So, its important to realize that function and data type deduction ‘looks’ at the whole content, including code that comes later. Function arguments that are not explicitly typed are **generic arguments** (think ala C++ templates but without the template keywording); these will prove very useful as they allow definition of functions that are flexible in their argument types. But enough about general F# typing for now, let’s fix our problem.

**TIP:** You can explicitly type function arguments to force their interpretion within the function body, and then fix invocations. Once all that works you can try to remove these declarations to make the code neater and more flexible — although sometimes F# does need type ‘hints’

So how are we going to develop `utility`

? Let’s re-write `utility`

such that it accepts an array of floats and an array of `DiscreteVar`

like so …

1: 2: 3: 4: 5: 6: 7: 8: |
let utility u v = let products = Array.map2 (fun uElem vElem -> uElem * match vElem.Setting with | One -> 1.0 | _ -> 0.0 ) u v Array.sum products let tv = utility utilities vars printfn "%f" tv |

In the mapping function, the second argument is still a `DiscreteVar`

but we use **pattern matching** to derive a float value from the discriminated union type. In this case, the pattern `One`

produces a 1.0 and otherwise a 0.0 results since `Unset`

and `Zero`

will match the ` default pattern. The result is that `

`products`

is an array of floats such that it is the pairwise product of the ` array and `

`1.0`

whereever `vElem.Setting`

is `One`

. Each member of this result (which is a `float array`

) is then summed using the `Array.sum`

method to produce a float result (remember that the final expression of a function is its return value). If we run this and examine the result as is we get 0.0, of course this is because all of the variables are `Unset`

and thus a multicand of 0.0 is produced for each `vElem`

in the expression.

We next realize that multiplying weights and the variables is the same function, so let’s rename it `multiplySum`

. We also don’t need the intermediate value `products`

and instead **pipe ( |>** the result of the pairwise multiplication to the Array.sum. We could apply this function using either

`utility`

or `weight`

as the first argument.
1: 2: 3: 4: 5: 6: 7: |
let multiplySum x v = Array.map2 (fun xElem vElem -> xElem * match vElem.Setting with | One -> 1.0 | _ -> 0.0 ) x v |> Array.sum printfn "%f" (multiplySum utilities vars) printfn "%f" (multiplySum weights vars) |

The `printfn`

function examines the format string and makes sure that the number and type of arguments match the wildcard patterns

In the `multiplySum printfn`

statements, you must use parens to make the function application with its arguments into a single argument for the print, otherwise its *three arguments*. Of course, both of these would still produce 0.0.

We have a sense of some of the code that we may use, but then notice that the arrays for variables, weights and utility be the same length. Furthermore the next incremental change is then realizing that weight and utility are really factors of a knapsack item. Therefore, we adjust the definitions and get to this:

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: |
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; mutable Setting : ZeroOneVarSetting; // discrete value setting } 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 = 20.0 let multiplySumWeight v = Array.map (fun vElem -> vElem.Weight * match vElem.Setting with | One -> 1.0 | _ -> 0.0 ) v |> Array.sum let multiplySumUtility v = Array.map (fun vElem -> vElem.Utility * match vElem.Setting with | One -> 1.0 | _ -> 0.0 ) v |> Array.sum printfn "%f" (multiplySumWeight vars) printfn "%f" (multiplySumUtility vars) |

OK, the item definitions seem better suited to the domain at hand — using the type system hand-in-hand with the problem domain is called domain driven design. One concept from this idea is to use the type system to disallow invalid states or variable settings. We see this in small part in the `ZeroOneVarSetting`

as the only choices are one of the three discriminants. If instead we used, say, an `int`

type to represent the state of our variable, assuming that the value would be -1 for Unset, 0 for Zero and 1 for One, what would the meaning be if the int took on a value of 7 or -4? In the F# discriminated union approach, there is simply no opportunity for such erroneous or unexpected settings.

Unfortunately however, the two functions for getting the total weight and utility seem redundant varying only in which record member is used in the multiplication. Let’s revisit this again, this time creating a two argument function `multiplyEither`

with a first argument of boolean that selects Utility if true and Weight otherwise.

1: 2: 3: 4: 5: 6: |
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 |

The *idiomatic* or *canonical* approach to leveraging functional style is often discussed in regard to F# code. See this and this for more

We then create our `multiplySumWeight`

as `multiplyEither true`

and `multiplySumUtility false`

. This demonstrates an important feature of functional programming, namely **partial function application**. `multiplySumWeight`

is `multiplyEither`

with the first argument of true ‘baked in’; it only has one argument now the `DiscreteVar`

array. That is the type for `multiplySumWeight`

is `DiscreteVar [] -> float`

, the type for `multiplyEither`

is `bool -> DiscreteVar [] -> float`

. Notice that we never told `multiplyEither`

what type its argument ` is, a Boolean type (bool) is deduced from its use as the test in the if expression (and the partial applications that use actual arguments of true and false). There are other variations for doing these accumulating products that could be used too, but for now we will leave it here.`

Download This Code

With some of the functionality we need to address the problem, on to page 2