Optex: an Elixir library for modeling and solving mixed-integer linear programs, with an in-process HiGHS binding via Rustler.
Build a model with the Optex.DSL, then call optimize/2:
iex> import Optex.DSL
iex> m =
...> model sense: :max do
...> variable x, lb: 0.0
...> variable y, lb: 0.0
...> constraint x + 2 * y <= 4
...> constraint 3 * x + y <= 6
...> objective x + y
...> end
iex> {:ok, sol} = Optex.optimize(m)
iex> sol.status
:optimal
iex> Float.round(sol.objective, 6)
2.8
iex> {Float.round(sol.values[:x], 6), Float.round(sol.values[:y], 6)}
{1.6, 1.2}Indexed variables are read back by the same key used to declare them:
sol.values[{:y, 2}] for variable y[i], i <- [1, 2, 3].
Summary
Functions
Explain why a model is infeasible.
Transform the model to solver input, solve it, and return the solution with values keyed by the user-facing variable names.
Functions
@spec explain_infeasibility( Optex.Model.t(), keyword() ) :: {:ok, %{ constraints: list(), variables: list(), constructs: [{atom(), term()}], not_examined: [atom()] }} | {:error, term()}
Explain why a model is infeasible.
Computes an irreducible infeasible subsystem (IIS): a minimal set of
constraints and variable bounds that is infeasible together (for MIPs, of
the LP relaxation). Returns {:ok, %{constraints: [...], variables: [...], constructs: [...], not_examined: [...]}} where constraint/variable
members are {name_or_id, involvement} (involvement says which side
participates: :lower, :upper, :boxed, ...) and construct members
are {kind, name_or_id} (defined variables report under their result
variable's name). Empty lists mean no IIS was found among what was
examined (the model is feasible or the search failed).
Scope depends on the backend. A construct-aware backend (Gurobi, via its
native IIS over general and quadratic constraints) examines the FULL
model, and conflicting constructs land in constructs. Everywhere else
the IIS examines the linear relaxation: constructs (indicators,
abs/pwl/min-max definitions, quadratic constraints) are stripped before
analysis, so any IIS found is genuine, and not_examined names the
stripped construct kinds since the real conflict may live there.
Options: :solver as in optimize/2; the backend must export the optional
iis/2 callback of the Optex.Solver behaviour or {:error, :not_supported} is returned.
@spec optimize( Optex.Model.t(), keyword() ) :: {:ok, Optex.Solution.t()} | {:error, term()}
Transform the model to solver input, solve it, and return the solution with values keyed by the user-facing variable names.
Options:
:solver- a module implementing theOptex.Solverbehaviour. Defaults toOptex.Solver.HiGHS(the only backend in v1; the option is the seam a future backend slots into).
Any remaining options are passed to the solver; every backend
understands :time_limit, :mip_gap, :threads, :log, :cancel,
and the MIP streaming options :progress (a pid receiving throttled
{:optex_progress, map} messages), :progress_every (throttle in ms,
default 1000, 0 = unthrottled), and :incumbents (a pid receiving
{:optex_incumbent, %{objective: o, values: by_name}} for each
improving solution; the values are rekeyed by variable name through a
relay process this function manages). Optex.Solver.Gurobi additionally
accepts qcp_duals: true to return quadratic constraint duals (in
Optex.Solution.qcon_duals, keyed by qconstraint name); backends
without that capability reject the option with
{:error, {:unsupported, :qcp_duals, backend}}.
Values are keyed by each variable's name: the bare atom for scalar
variables (:x), {family, index} for indexed families ({:y, 1},
{:w, {1, :a}}). A variable created without a name falls back to its
integer id.