Calling a Linear Solver C Library from Go
I wrote the golp library to provide Go bindings via cgo for the LP_Solve open source Mixed Integer Linear Programming (MILP) solver.
The glue between Go and C
Here's the part of golp's lp.go that let's it compile for and link to LP Solve's C library:
/*
#cgo linux CFLAGS: -I./lpsolve
#cgo linux LDFLAGS: -L./lpsolve -llpsolve55 -Wl,-rpath=./lpsolve
#cgo darwin CFLAGS: -I/opt/local/include/lpsolve
#cgo darwin LDFLAGS: -L/opt/local/lib -llpsolve55
#include "lp_lib.h"
...
*/
import "C"
...
The import "C"
is similar to a normal Go import in that it brings in the C
package with functions you can call, but cgo also interprets the comment above import "C"
in a special way as follows.
The #cgo
lines with CFLAGS
and LDFLAGS
specify flags for the C compiler and linker respectively. The darwin
and linux
are Go build constraints that specify which platform those flags apply to.
The linux
flags indicate that the compiler should search ./lpsolve
for the header and library file, and the -Wl-rpath=./lpsolve
will cause the resulting executable to search the ./lpsolve
folder for lpsolve55.so
to dynamically link to it. The Darwin build flags are designed to work with the version of LPSolve that will be installed by MacPorts.
After the #cgo
lines, the rest of the comment is interpreted as a snippet of C code, which can include headers and can even define new functions in C for the Go code to call.
Using C types
With the initial glue in place, we can reference C types with C.[any type defined in your C snippet]
. For instance, golp's wrapper LP
type looks like this, where lprec
is the type name for a linear program instance in LP Solve:
type LP struct {
ptr *C.lprec
}
Calling C functions
Calling C functions from Go is also as easy as calling C.[c function]
. Here's golp's NewLP
that returns a new linear program instance by calling C.make_lp
(where make_lp
is a function in lp_lib.h
):
func NewLP(rows, cols int) *LP {
l := new(LP)
l.ptr = C.make_lp(C.int(rows), C.int(cols))
runtime.SetFinalizer(l, deleteLP)
...
return l
}
Notice the runtime.SetFinalizer
call. In Go, objects are garbage collected, so we don't need to explicitly free them, but make_lp
is a C function and allocates memory outside of Go's garbage-collecting watch. Thus we should free it by calling LP Solve's delete_lp
(which the Go wrapper deleteLP
does).
The effect of doing the delete in the finalizer is that we can now use the Go LP
wrapper object without needing to worry about calling the cleanup function explicitly as the Go runtime will call it right before it garbage collects the wrapper object.
Another thing to note are the type casts like C.int(rows)
, which are needed because int
in Go and C are considered different types and could be different sizes depending on the platform and compilers.
Installing golp
To use the golp library, you'll need to:
1. Get the golp Go code
go get github.com/draffensperger/golp
2. Get LPSolve
For Linux, download the build from SourceForge and extract it to ./lpsolve
. E.g. for 64-bit,
LP_URL=https://sourceforge.net/projects/lpsolve/files/lpsolve/5.5.2.0/lp_solve_5.5.2.0_dev_ux64.tar.gz
mkdir lpsolve
curl -L $LP_URL | tar xvz -C lpsolve
For OS X, install MacPorts then sudo port install lp_solve
. There are Windows builds on SourceForge as well.
Using it in your project
Here's a sample program that uses golp
to solve a simple linear program, based on an example from the LPSolve documentation:
package main
import "fmt"
import "github.com/draffensperger/golp"
func main() {
lp := golp.NewLP(0, 2) // 0 rows by 2 columns
lp.AddConstraint([]float64{110.0, 30.0}, golp.LE, 4000.0)
lp.AddConstraint([]float64{1.0, 1.0}, golp.LE, 75.0)
lp.SetObjFn([]float64{143.0, 60.0})
lp.SetMaximize()
lp.Solve()
vars := lp.Variables()
fmt.Printf("Plant %.3f acres of barley\n", vars[0])
fmt.Printf("And %.3f acres of wheat\n", vars[1])
fmt.Printf("For optimal profit of $%.2f\n", lp.Objective())
// No need to explicitly free underlying C structure as
// golp.LP finalizer will
}
Outputs:
Plant 21.875 acres of barley
And 53.125 acres of wheat
For optimal profit of $6315.62
Other Linear Programming options for Go
LPSolve is LGPL licensed, which allows it to be called from closed-source projects. It also supports solving mixed-integer programming problems (where some variables must be integers) using branch-and-bound. See the golp README for an mixed-integer example.
There are also Go bindings for the GPL-licensed GNU Linear Programming Kit (GLPK) at github.com/lukpank/go-glpk, which would be suitable if your project has a GPL-compatible open source license.
There are a number of other commercial and open-source solvers like CBC, CLP, GLOP, Gurobi, CPLEX, SCIP, and Sulum. Google provides a unified SWIG compatible C++ interface for all those solvers in their or-tools project.
There is Go support for SWIG bindings, so it should be possible to write a wrapper that would connect to those other solvers via the or-tools library. It would involve some additional glue code compared to calling a C library with cgo though.