Skip to content
Testing out a light wrapper for the Gurobi optimizaer
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Examples
GurobiFsharp
.gitattributes
.gitignore
GurobiFsharp.sln
LICENSE
README.md

README.md

Gurobi Fsharp Library

The purpose of this library is to create an idiomatic F# wrapper around the Gurobi .NET library. The reason for this is that F# is a functional-first language and the composition model favors functions over methods. F# is perfectly capable of working with objects and their methods but I prefer function composition so I decided to create my own wrapper around the Gurobi object model to enable that.

Design Note

Gurobi provides a Python library for their solver which allows for the simple and elegant building of models. I am purposefully emulating the style of that library. I wanted to bring the beauty of that library to F#. Hats off to the Gurobi team for providing an excellent blueprint to emulate.

Note This is a work in progress. Support will expand as I use the tool more and need to integrate additional capabilities.

Netflow Example

The following shows an example of a network flow problem provided by Gurobi and modeled in Python. The full formulation can be found here. In this example I am just comparing and contrasting the Python and F# constraint formulation methods.

Disclaimer: All Python code is copyrighted by Gurobi Optimization, LLC

Creating a model

Python

In Python the creation of the model and decision variables is quite straightforward.

# Copyright 2018, Gurobi Optimization, LLC

# Create optimization model
m = Model('netflow')

# Create variables
flow = m.addVars(commodities, arcs, obj=cost, name="flow")

F#

In F# we have a similar syntax but instead of flow being a Dictionary of decision variables, we produce a Map<string list, GRBDecVar> which is essentially the same for our purposes.

// Create a new instance of the Gurobi Environment object
// to host models
let env = Environment.create

// Create a new model with the environment variable
let m = Model.create env "netflow"

// Create a Map of decision variables for the model
// addVarsForMap <model> <lower bound> <upper bound> <type> <input Map>
let flow = Model.addVarsForMap m 0.0 INF CONTINUOUS costs

Instead of using the methods on the object, functions have been provided which operate on the values that are passed in. This is more idiomatic for F#. The Model module in the library hosts all of the functions for working with objects of type Model.

The Model.adddVarsForMap function takes a Map<string list, float> and produces a Map<string list, GRBDecVar> for the modeler to work with. This is similar to how the Python tuples are working in the gurobipy library. Instead of indexing into a Python dictionary with tuples, F# uses a string list as the index.

Adding Constraints

Python

The gurobipy library offers a succinct way of expressing a whole set of constraints by using generators. There is additional magic going on under the hood though that may not be obvious at first. The following method generates a set of constraints for each element in arcs but also creates a meaningful constraint name. The prefix for the constraint name is the last argument of the method ("capacity" in this instance).

# Arc capacity constraints
capacityConstraints = 
    m.addConstrs(
        (flow.sum('*',i,j) <= capacity[i,j] for i,j in arcs), "capacity")

There is also special sauce occuring in the flow.sum('*',i,j) syntax. flow is a dictionary which is indexed by a 3 element tuple. What this sum() method is doing is summing across all elements in the dictionary which fit the pattern. The * symbol is a wildcard and will match against any element. This is a powerful way to sum across dimensions of the optimization model.

F#

In F# we can do something similar but instead of having a generator we pass in a lambda to create the constraints.

let capacityConstraints =
    Model.addConstrs m "capacity" arcs
        (fun [i; j] -> (sum flow ["*"; i; j] <== capacity.[[i; j]]))

The function Model.addConstrs takes a model object as its first argument (m in this case), the prefix for what the constraints are going to be named ("capacity" in this case), and the set of indices the constraints will be created over, arcs in this case. The key point is that the types of the indices must match the input type of the lambda.

The addConstrs function will iterate through each of the indices in the set, create a constraint from the lambda that was passed, and name the constraint appropriatly. If the first element of the arcs set was ["Detroit"; "Boston"] then the name of the first constraint would be capacity_Detroit_Boston. This helps the modeler by maintaining a consistent naming scheme for the constraints in the model.

You can’t perform that action at this time.