Mathematical Optimization for Elixir β Write optimization problems naturally, like mathematical notation, with automatic linearization and powerful pattern-based modeling.
βΉοΈ Two supported APIs: The DSL (
Problem.define doβ¦end) is ideal for static problems; the Functional API (Problem.new_variable,Problem.add_constraint, etc.) is the right choice for dynamic/runtime problems. OnlyProblem.maximize/2andProblem.minimize/2are deprecated. See Choosing an API Style and the Deprecation Notice.
require Dantzig.Problem, as: Problem
# Pattern-based N-dimensional modeling
problem =
Problem.define do
new(direction: :maximize)
# Create x[i,j] for i=1..8, j=1..8 β that's 64 variables in one line!
variables("x", [i <- 1..8, j <- 1..8], :binary, "Queen position")
# One queen per row: sum over j for each i
constraints([i <- 1..8], sum(x[i][:_]) == 1, "One queen per row")
# One queen per column: sum over i for each j
constraints([j <- 1..8], sum(x[:_][j]) == 1, "One queen per column")
# Maximize queens placed (will be 8 for valid N-Queens solution)
objective(sum(x[:_][:_]), direction: :maximize)
end
{:ok, solution} = Dantzig.solve(problem)Pattern-Based Modeling: Create N-dimensional variables with intuitive generator syntax
# Instead of manually creating x11, x12, x13, x21, x22, x23, ...
variables("x", [i <- 1..3, j <- 1..3], :binary)
# Use natural patterns: x[i][:_] sums over second dimension
constraints([i <- 1..3], sum(x[i][:_]) == 1, "Row constraint")Automatic Linearization: Non-linear expressions become linear constraints automatically
# These work out of the box β no manual linearization needed!
constraints(abs(x) + max(x, y, z) <= 5, "Non-linear with auto-linearization")
constraints(a AND b AND c, "Logical AND constraint")Model Parameters: Pass runtime data directly into your optimization models
# Use a map with integer keys matching the generator range
# Bracket notation is used uniformly: costs[i] for constants, x[i] for variables
params = %{costs: %{1 => 10, 2 => 20, 3 => 30}, capacity: 100}
problem = Problem.define(model_parameters: params) do
variables("x", [i <- 1..3], :integer, min_bound: 0)
constraints(sum(for i <- 1..3, do: costs[i] * x[i]) <= capacity, "Budget")
objective(sum(for i <- 1..3, do: x[i]), :maximize)
endProblem.modify: Build problems incrementally
# Start with base problem
base = Problem.define do
variables("x", [i <- 1..3], :continuous)
constraints([i <- 1..3], x[i] >= 0)
end
# Add more constraints and variables
problem = Problem.modify(base) do
variables("y", [j <- 1..2], :binary)
constraints(x[1] + x[2] + x[3] <= 10, "Capacity")
objective(x[1] + 2*x[2] + 3*x[3], :maximize)
endMultiple Modeling Styles: Choose the approach that fits your problem
- Simple syntax for basic problems
- Pattern-based for N-dimensional problems
- Model parameters for configurable problems
- Problem.modify for incremental building
- Explicit control when you need it
- AST transformations for advanced use cases
Add to your mix.exs:
def deps do
[
{:dantzig, "~> 0.2.0"}
]
endrequire Dantzig.Problem, as: Problem
problem =
Problem.define do
new(direction: :maximize)
variables("x", :continuous, min_bound: 0, description: "Items to produce")
variables("y", :continuous, min_bound: 0, description: "Items to sell")
constraints(x + 2*y <= 14, "Resource constraint")
constraints(3*x - y <= 0, "Quality constraint")
objective(3*x + 4*y, direction: :maximize)
end
{:ok, solution} = Dantzig.solve(problem)
IO.inspect({solution.objective, solution.variables})| Level | Guide | Description |
|---|---|---|
| π Beginner | Quick Start | Your first optimization problem |
| π Intermediate | Tutorial | Complete guide with examples |
| ποΈ Advanced | Architecture | System design deep dive |
| π§ Reference | API Docs | Complete function reference |
Generate full docs locally:
mix docsCreate complex variable structures with simple generators:
# 2D transportation problem: supply[i] to demand[j]
variables("transport", [i <- 1..3, j <- 1..4], :continuous, min_bound: 0)
# 3D production planning: product[p] in period[t] at plant[f]
variables("produce", [p <- products, t <- 1..12, f <- facilities], :integer, min_bound: 0)
# 4D chess tournament: player[a] vs player[b] in round[r] at table[t]
variables("game", [a <- 1..8, b <- 1..8, r <- 1..7, t <- 1..4], :binary)Non-linear expressions become linear constraints behind the scenes:
# Absolute values
constraints(abs(inventory) <= max_inventory, "Absolute inventory")
# Maximum functions
constraints(max(profit1, profit2, profit3) >= target, "Max profit")
# Minimum functions
constraints(min(cost1, cost2) <= budget, "Min cost")
# Logical operations (binary variables)
constraints(decision1 AND decision2 AND decision3, "All decisions required")
constraints(decision1 OR decision2 OR decision3, "At least one decision")Express complex constraints naturally:
# Sum over specific dimensions
constraints([i <- 1..5], sum(x[i][:_]) == 1, "One per row")
constraints([j <- 1..5], sum(x[:_][j]) == 1, "One per column")
# Complex aggregations
constraints([i <- 1..3], sum(x[i][:_]) >= demand[i], "Demand satisfaction")
# Multi-dimensional constraints
constraints([i <- 1..3, j <- 1..3], x[i][j] <= capacity[i][j], "Capacity limits")Complete, runnable examples are available in the Examples Directory:
- Resource Allocation:
mix run docs/user/examples/simple_working_example.exs - Knapsack:
mix run docs/user/examples/knapsack_problem.exs - Assignment:
mix run docs/user/examples/assignment_problem.exs
- Transportation:
mix run docs/user/examples/transportation_problem.exs - Production Planning:
mix run docs/user/examples/production_planning.exs - Blending:
mix run docs/user/examples/blending_problem.exs
- N-Queens:
mix run docs/user/examples/nqueens_dsl.exs - Pattern Operations:
mix run docs/user/examples/pattern_based_operations_example.exs - Network Flow:
mix run docs/user/examples/network_flow.exs
See the Examples Directory for a complete categorized list of all examples.
Dantzig exposes two current, supported APIs and one deprecated one. Understanding the difference helps you pick the right tool.
A compile-time macro that lets you write optimization problems in a declarative, mathematical style. Internally it expands to the Functional API (below) via DSLReducer.
require Dantzig.Problem, as: Problem
problem = Problem.define do
new(direction: :maximize)
variables("x", [i <- 1..3], :continuous, min_bound: 0)
constraints([i <- 1..3], x[i] <= 10, "Bound")
objective(sum(x[:_]), direction: :maximize)
endUse the DSL when:
- The problem structure is statically known at compile time
- You want concise, mathematical notation
- You're building textbook-style problems (knapsack, N-queens, blending, etc.)
Limitation: outer-scope Elixir variables referenced inside max_bound:, description:, or
string-interpolated constraint names may not resolve correctly β they are captured as AST, not
evaluated values. Use model_parameters: to pass runtime data into the DSL:
Problem.define(model_parameters: %{capacity: 100}) do
new(direction: :minimize)
variables("x", [i <- 1..3], :continuous, min_bound: 0)
constraints(sum(x[:_]) <= capacity, "Capacity") # capacity resolved at runtime β
objective(sum(x[:_]), direction: :minimize)
endRegular Elixir functions that Problem.define expands to. Fully public, supported, and the right
choice for dynamic problems where the shape (number of variables, constraints) is determined at
runtime β for example, from database records or user input.
Key functions:
alias Dantzig.{Polynomial, Constraint}
require Dantzig.Constraint, as: Constraint
problem = Problem.new(direction: :maximize)
# Add variables one at a time; each returns {updated_problem, polynomial_handle}
{problem, x} = Problem.new_variable(problem, "x", type: :continuous, min_bound: 0)
{problem, y} = Problem.new_variable(problem, "y", type: :continuous, min_bound: 0)
# Build the objective incrementally
problem = problem
|> Problem.increment_objective(Polynomial.multiply(x, 3))
|> Problem.increment_objective(Polynomial.multiply(y, 4))
# Add constraints (Constraint.new_linear is a macro β require it)
problem = Problem.add_constraint(problem,
Constraint.new_linear(x + 2*y <= 14, name: "resource"))
{:ok, solution} = Dantzig.solve(problem)Use the Functional API when:
- The problem shape depends on runtime data (e.g. from a database or API)
- You're building a helper that programmatically generates variables or constraints in a loop
- You need fine-grained control over variable naming
The following functions still exist but are deprecated and will be removed in v1.0.0:
# β DEPRECATED β sets objective but discards any previously accumulated terms
problem = Problem.maximize(problem, 3*x + 4*y)
problem = Problem.minimize(problem, cost)Replace with:
# β
Set direction in new/1, build objective with increment_objective/2
problem = Problem.new(direction: :maximize)
problem = Problem.increment_objective(problem, Polynomial.multiply(x, 3))
problem = Problem.increment_objective(problem, Polynomial.multiply(y, 4))
# or set it all at once with set_objective/2 + direction in new/1
problem = Problem.set_objective(problem, 3*x + 4*y)The old add_variables/5 macro (with explicit problem as first arg) is similarly deprecated in
favour of variables/5 inside a define block or Problem.new_variable/3 in the Functional API.
World-Class Optimization Power: Dantzig integrates seamlessly with the HiGHS solver, providing access to cutting-edge optimization algorithms.
Automatic Binary Management: The HiGHS solver binary is automatically downloaded and managed for your platform.
High Performance: Leverages HiGHS's advanced algorithms for linear programming, mixed-integer programming, and quadratic programming.
# Automatic HiGHS integration - no manual setup required!
{:ok, solution} = Dantzig.solve(problem)
# Custom configuration (optional)
config :dantzig, :highs_binary_path, "/usr/local/bin/highs"
config :dantzig, :highs_version, "1.9.0"
# Advanced solver options
config :dantzig, :solver_options, [
parallel: true,
presolve: :on,
time_limit: 300.0
]Dantzig automatically downloads the HiGHS solver binary for your platform:
# Custom binary path (optional)
config :dantzig, :highs_binary_path, "/usr/local/bin/highs"
# HiGHS version (default: "1.9.0")
config :dantzig, :highs_version, "1.9.0"| Feature | Status | Notes |
|---|---|---|
| Linear Programming | β Complete | Full support |
| Quadratic Programming | β Complete | Degree β€ 2 expressions |
| Pattern-based Modeling | β Complete | N-dimensional variables |
| Automatic Linearization | β Complete | abs, max/min, logical ops |
| Mixed-Integer Variables | Types defined, LP serialization pending | |
| Custom Operators | π§ Reserved | :in operator for future use |
We welcome contributions! Explore the architecture docs to understand the system design, and feel free to submit issues and pull requests on GitHub.
Key Areas for Contribution:
- Mixed-integer LP serialization
- Additional non-linear function support
- Performance optimizations
- Documentation and examples
MIT License - see LICENSE.TXT for details.
- HiGHS - World-class optimization solver
- JuliaBinaryWrappers - Pre-compiled binaries
- Elixir Community - Inspiration and valuable feedback
Ready to optimize? Start with the Quick Start Guide or dive into the Tutorial for comprehensive examples!