Using Solvers

This guide covers how to use LumiX’s unified solver interface to solve optimization models with different solvers.

Introduction

LumiX provides a solver-agnostic interface that allows you to:

  • Switch between solvers with a single line of code

  • Leverage solver-specific features when needed

  • Use the same model with different solvers

  • Automatically detect solver capabilities

Philosophy

Traditional Approach (Solver-Specific)

Traditional optimization libraries require solver-specific code:

# Gurobi-specific code
import gurobipy as gp
m = gp.Model()
x = m.addVar(name="x")
m.setObjective(x, GRB.MAXIMIZE)
m.optimize()

# Can't easily switch to CPLEX without rewriting

LumiX Approach (Solver-Agnostic)

LumiX uses a unified interface:

from lumix import LXModel, LXOptimizer

# Build model once
model = LXModel("example").add_variable(x).maximize(obj)

# Switch solvers with one line
optimizer = LXOptimizer().use_solver("gurobi")
# optimizer = LXOptimizer().use_solver("ortools")
# optimizer = LXOptimizer().use_solver("cplex")

solution = optimizer.solve(model)

Benefits:

  • ✓ Write once, solve anywhere

  • ✓ Easy to compare solver performance

  • ✓ Graceful degradation (free → commercial)

  • ✓ Capability-aware automatic linearization

Core Components

The solvers module consists of three main components:

        graph LR
    A[Your Model] --> B[LXOptimizer]
    B --> C[LXSolverInterface]
    C --> D[Solver Implementation]
    E[LXSolverCapability] --> C

    style A fill:#e8f4f8
    style B fill:#e1f5ff
    style C fill:#fff4e1
    style D fill:#e1ffe1
    style E fill:#ffe1e1
    
  1. LXOptimizer: High-level interface for configuring and solving models

  2. LXSolverInterface: Abstract base class defining solver contract

  3. Solver Implementations: Concrete implementations for each solver (OR-Tools, Gurobi, CPLEX, GLPK, CP-SAT)

  4. LXSolverCapability: Describes what features each solver supports

Quick Start

Basic Usage

from lumix import LXModel, LXOptimizer

# Build your model
model = build_production_model(products)

# Create optimizer and select solver
optimizer = LXOptimizer().use_solver("ortools")

# Solve
solution = optimizer.solve(model)

# Access results
if solution.is_optimal():
    print(f"Objective: {solution.objective_value}")
    for var_name, value in solution.variable_values.items():
        print(f"{var_name} = {value}")

Solver Selection

Choose a solver based on your needs:

# Free, open-source (good for most problems)
optimizer = LXOptimizer().use_solver("ortools")

# Commercial, high-performance (best for large problems)
optimizer = LXOptimizer().use_solver("gurobi")
optimizer = LXOptimizer().use_solver("cplex")

# Free, basic (small problems only)
optimizer = LXOptimizer().use_solver("glpk")

# Constraint programming (scheduling/assignment)
optimizer = LXOptimizer().use_solver("cpsat")

Solver Parameters

Pass solver-specific parameters:

# Gurobi example
solution = optimizer.solve(
    model,
    time_limit=300,        # 5 minutes
    gap_tolerance=0.01,    # 1% gap
    Threads=4,             # Use 4 threads
    MIPFocus=1,            # Focus on feasibility
    LogToConsole=1,        # Show solver log
)

Components Details

LXOptimizer

The main interface for solving models:

Key Methods:

  • use_solver(name): Select solver (“ortools”, “gurobi”, “cplex”, “glpk”, “cpsat”)

  • enable_rational_conversion(): Convert floats to rationals for integer solvers

  • enable_linearization(): Automatically linearize nonlinear terms

  • enable_sensitivity(): Enable sensitivity analysis

  • solve(model, **params): Solve the model

Example:

optimizer = (
    LXOptimizer()
    .use_solver("gurobi")
    .enable_sensitivity()
    .enable_rational_conversion()
)

solution = optimizer.solve(model, time_limit=600)

Solver Capabilities

Query what features a solver supports:

from lumix import GUROBI_CAPABILITIES, ORTOOLS_CAPABILITIES

# Check capabilities
print(GUROBI_CAPABILITIES.description())
# Gurobi: Linear Programming, Mixed-Integer Programming,
#         Quadratic Programming, Second-Order Cone Programming

# Check specific features
if GUROBI_CAPABILITIES.can_solve_quadratic():
    print("Gurobi supports quadratic programming")

if ORTOOLS_CAPABILITIES.needs_linearization_for_bilinear():
    print("OR-Tools needs linearization for x*y products")

Automatic Linearization

LumiX can automatically linearize nonlinear terms for solvers that don’t support them:

from lumix import LXOptimizer

# Model with bilinear terms (x * y)
model = build_model_with_bilinear_terms()

# Enable automatic linearization
optimizer = (
    LXOptimizer()
    .use_solver("ortools")  # Doesn't support quadratic
    .enable_linearization(
        big_m=1e6,
        pwl_segments=20,
        mccormick_tighten_bounds=True
    )
)

# Solver automatically linearizes bilinear terms
solution = optimizer.solve(model)

Guide Sections

Next Steps