eduardosalaz

eduardosalaz

Optex - mathematical optimization (LP/MILP/QP/SOCP) with in-process solvers

I’ve released Optex, a library for modeling and solving mathematical optimization problems from Elixir, with the solvers running in-process via Rustler rather than behind a service call.

If you’ve ever needed to answer “what’s the cheapest way to do this given these constraints”: scheduling shifts, routing deliveries, allocating inventory, picking which warehouses to open, that’s a linear or mixed-integer program, and the answer until now was to stand up a Python service with PuLP or gurobipy and talk to it over HTTP. Optex removes that boundary.

It works in Livebook with no toolchain:

Mix.install([{:optex, "~> 0.1.1"}])

HiGHS ships precompiled for x86_64/aarch64 Linux, x86_64/aarch64 macOS, and x86_64 Windows, so that’s a checksummed binary download and you’re solving MILPs in a notebook. No Rust, no CMake.

What a model looks like:

import Optex.DSL

m =
  model sense: :max do
    variable take[i], i <- items, type: :bin
    constraint sum(weight[i] * take[i], i <- items) <= capacity
    objective sum(value[i] * take[i], i <- items)
  end

{:ok, sol} = Optex.optimize(m)
sol.objective       #=> optimal value
sol.values[{:take, 3}]

The DSL is macro-based, so it reads your expressions structurally at compile time. Trailing generators declare families (one row per binding) and options are evaluated per binding with the generators in scope, so bounds and types can depend on the index.

Where the BEAM actually earns its keep:

Models are immutable values. There’s no solver handle to guard, no environment to pool, so a scenario study is just data work:

scenarios
|> Task.async_stream(&Optex.optimize(build_model(&1)))
|> Enum.to_list()

Solves run on dirty schedulers, so a long branch-and-bound doesn’t stall your schedulers. And instead of C callbacks, progress and improving solutions arrive as ordinary messages:

Optex.optimize(m, progress: self(), incumbents: self())
# {:optex_progress, %{best_obj: _, best_bound: _, gap: _, nodes: _}}
# {:optex_incumbent, %{objective: _, values: _}}

Combine that with a cancel token and a stopping rule is a receive loop in your own process. It’s the same code on every backend, it can’t crash the solver, and it drops straight into a LiveView if you want a progress bar on a running solve.

Scope: LP, MILP, QP, QCP, SOCP. Native indicator constraints, absolute values, piecewise-linear functions, min/max, and SOS on backends that support them. Duals and reduced costs for LPs, IIS-based infeasibility diagnosis, LP/MPS export. HiGHS is built in; Gurobi, CPLEX, and COPT compile if you have them installed and licensed.

One opinionated bit: if you ask for something your chosen solver doesn’t natively support, it fails before solving rather than quietly rewriting your model into a big-M formulation. JuMP will bridge those automatically; Optex refuses on purpose, because a silent rewrite needs bounds you may not have given and can wreck solve times in ways that surface as a production mystery instead of an error. The examples show the manual reformulations side by side with the native constructs so you can pick with both costs visible.

There are 23 runnable examples in the repo and a Livebook tour. Longer design rationale in docs/design_notes.md, and there’s an Elixir primer aimed at people arriving from JuMP or gurobipy.

It’s v0.1.1 and I’d genuinely like to hear from anyone who puts it near a real problem. The biggest known gap is persistent solver handles and warm starts for re-solve loops, I’ve deliberately not designed that yet because I’d rather build it against someone’s actual mutation patterns than guess.

Where Next?

Popular in Announcing Top

Qqwy
Hello everyone, I wrote a small library today called MapDiff. It returns a map listing the (smallest amount of) changes to get from map...
New
alisinabh
Hey everyone i’ve developed a library for Jalaali calendar for elixir which supports converting Gregorian dates to Jalaali and vice vers...
New
josevalim
EDIT: since Ecto 3.0 final version is out, this post was amended to use the final versions in the instructions below. Hi everyone, We a...
New
RobertDober
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as...
239 12978 134
New
benlime
LiveMotion enables high performance animations declared on the server and run on the client. As a follow up to my previous thread A libr...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
markmark206
simple_feature_flags is a tiny package that lets you turn features on or off based on which environment (e.g. localhost, staging, product...
New

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 49266 226
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New

We're in Beta

About us Mission Statement