Dantzig - Linear programming for Elixir (using the open source HiGHS solver)

I’ve written a wrapper for the HiGHS solver (currently only supports linux; the linux executable is bundled with the source in the /priv directory) to help with a data visualization library I’m building. It’s really helpful to be able to specify the relationships between objects in a drawing as declarative constraints and let the system solve everything before drawing. Plotting data is a case where there is a big mix of variable and constant terms, and most interesting constraints are linear or quadratic.

Source code here: GitHub - tmbb/dantzig: (Integer) linear (and quadratic) programming for Elixir (using the HiGHS solver)

You can find an example there in the README.

A simple example showing a very easy case of quadratic programming (which can be solved by hand very easily) is this:

defmodule Dantzig.Instances.ClosedFormQuadraticTest do
  use ExUnit.Case, async: true
  require Dantzig.Problem, as: Problem
  use Dantzig.Polynomial.Operators

  test "closed form quadratic: x - x*x" do
    problem = Problem.new(direction: :maximize)

    {problem, x} = Problem.new_variable(problem, "x", min: -2.0, max: 2.0)
    {problem, _obj} = Problem.increment_objective(problem, x - x*x)

    solution = Dantzig.solve(problem)

    assert solution.model_status == "Optimal"
    assert solution.feasibility == "Feasible"
    # There are no constraints
    assert solution.constraints == %{}
    # Only a single variable is created
    assert Solution.nr_of_variables(solution) == 1
    # The solution is correct (within a margin of error)
    assert_in_delta(Solution.evaluate(solution, x), 0.5, 0.0001)
    # The objective value is correct (within a margin of error)
    assert_in_delta(solution.objective, 0.5, 0.0001)
  end
end

The problem above can be written using an “implicit problem” macro like this:

  test "closed form quadratic: x - x*x (implicit problem)" do
    problem = Problem.new(direction: :maximize)

    Problem.with_implicit_problem problem do
      # Define a variable in the current problem
      v!(x, min: -2.0, max: 2.0)
      
      # x is a polynomial (actually a monomial, since it has only one term)
      %Polynomial{} = x
      # The objective function is also a polynomial (of degree 2 with 2 terms)
      %Polynomial{} = x - x*x
      
      _obj = Problem.increment_objective(x - x*x)
    end

    solution = Dantzig.solve(problem)
  end

The package (not yet published on hex) contains a library to define polynomials and algebraic operations on polynomials. We support polynomials of arbitrarily high degree, but only polynomials of degree < 3 can be used for constraints and for the objective function in the solver.

7 Likes

I have uploaded the first “public” release on hex.pm. The source tree embeds the highs executable for linux (the linear programming solver), so the package will only work on linux. I plan on adding support for downloading the right version for the user’s achitecture at compile-time (like rustler_precompiled and esbuild do).

Support for other architectures will come in a new version.

1 Like

Version 0.2.0

Verstion 0.2.0 is out! I no longer include the solver’s binary in the source, but have made changes so that it’s downloaded automactically from github releases at compile time. Dantzig doesn’t make HTTP requests at runtime to get the binary (or for anything else).

It still only supports Mac and Linux, but if someone is interested in adding support for windows please get in touch, it’s just a question of selecting the right HiGHS binary for the windows architecture and adding support for it.

Other than that, everything should be the same.

In order to keep everything simple, I don’t plan on adding support for alternative solvers unless there’s a really good reason for it.

2 Likes