LLM Integration#

discopt includes optional LLM-powered features for model explanation, natural language formulation, infeasibility diagnosis, conversational model building, and more.

These features use litellm as a universal adapter supporting 100+ LLM providers (Anthropic, OpenAI, Google, Ollama, etc.).

Important

LLM features are purely advisory — they never affect solver correctness. All formulations pass through model.validate() before use.

Setup#

Install the LLM dependencies:

pip install discopt[llm]

Set your API key for your chosen provider:

# Anthropic (default)
export ANTHROPIC_API_KEY="sk-ant-..."

# Or use any other provider:
export OPENAI_API_KEY="sk-..."
export DISCOPT_LLM_MODEL="openai/gpt-4o"

# Or use a local model (no API key needed):
export DISCOPT_LLM_MODEL="ollama/llama3"
import discopt.modeling as dm
import numpy as np
from discopt.llm import is_available

print(f"LLM features available: {is_available()}")
LLM features available: True

1. Model Serialization#

The serializer module converts discopt models and results into structured text that LLMs can understand. This is used internally by all LLM features.

from discopt.llm.serializer import serialize_model

# Build a simple model
m = dm.Model("facility_location")
x = m.continuous("shipment", shape=(3, 5), lb=0, ub=200)
y = m.binary("open", shape=(3,))

supply = np.array([100, 150, 200])
demand = np.array([80, 60, 70, 40, 50])
cost = np.random.RandomState(42).rand(3, 5) * 100

m.minimize(
    dm.sum(lambda i: dm.sum(lambda j: cost[i, j] * x[i, j], over=range(5)), over=range(3))
    + dm.sum(lambda i: 1000 * y[i], over=range(3))
)

# Demand satisfaction
for j in range(5):
    m.subject_to(
        dm.sum(lambda i: x[i, j], over=range(3)) >= demand[j],
        name=f"demand_{j}",
    )

# Supply capacity (only if open)
for i in range(3):
    m.subject_to(
        dm.sum(lambda j: x[i, j], over=range(5)) <= supply[i] * y[i],
        name=f"capacity_{i}",
    )

m.validate()
print(serialize_model(m))
# Model: facility_location

## Variables
- shipment (continuous, shape=(3, 5), lb=array((3, 5)), ub=array((3, 5)))
- open (binary, shape=(3,), lb=array((3,)), ub=array((3,)))

## Objective
minimize (Σ[3 terms] + Σ[3 terms])

## Constraints (8 total)
- [demand_0] (80 - Σ[3 terms]) <= 0.0
- [demand_1] (60 - Σ[3 terms]) <= 0.0
- [demand_2] (70 - Σ[3 terms]) <= 0.0
- [demand_3] (40 - Σ[3 terms]) <= 0.0
- [demand_4] (50 - Σ[3 terms]) <= 0.0
- [capacity_0] (Σ[5 terms] - (100 * open[3][0])) <= 0.0
- [capacity_1] (Σ[5 terms] - (150 * open[3][1])) <= 0.0
- [capacity_2] (Σ[5 terms] - (200 * open[3][2])) <= 0.0

## Statistics
- Total variables: 18
- Continuous: 15
- Integer/Binary: 3
- Constraints: 8

2. Solver Strategy Advisor#

The advisor analyzes model structure and recommends solver parameters using rule-based heuristics. With llm=True, it augments suggestions with LLM analysis.

from discopt.llm.advisor import suggest_solver_params

params = suggest_solver_params(m)
print("Recommended solver parameters:")
for key, value in params.items():
    print(f"  {key}: {value}")
Recommended solver parameters:
  reasoning: Using pure-JAX IPM (default); Enabling 4-partition McCormick (bilinear terms detected)
  nlp_solver: ipm
  partitions: 4
  cutting_planes: False
  branching_policy: fractional
  batch_size: 16
  gap_tolerance: 0.0001
  time_limit: 300

3. Auto-Reformulation Analysis#

Analyze a model for reformulation opportunities: big-M tightening, McCormick partitioning, symmetry breaking, bound tightening.

from discopt.llm.reformulation import analyze_reformulations

suggestions = analyze_reformulations(m)
print(f"Found {len(suggestions)} reformulation suggestions:\n")
for i, s in enumerate(suggestions, 1):
    print(f"{i}. [{s.category}] {s.description}")
    print(f"   Impact: {s.impact}\n")
Found 1 reformulation suggestions:

1. [mccormick_partitioning] Found bilinear terms in 3 constraints — use partitions=4 or partitions=8 for tighter McCormick relaxations
   Impact: Piecewise McCormick can significantly tighten relaxations for bilinear/pooling problems

4. Result Explanation (requires API key)#

After solving, use explain(llm=True) to get a rich, context-aware explanation. Falls back to a template string if the LLM is unavailable.

result = m.solve()
print(result.explain(llm=True))

5. Infeasibility Diagnosis (requires API key)#

When a model is infeasible, the diagnosis module identifies likely conflicting constraints and suggests minimal relaxations.

from discopt.llm.diagnosis import diagnose_infeasibility

# Create an intentionally infeasible model
m_bad = dm.Model("infeasible")
x = m_bad.continuous("x", lb=0, ub=10)
m_bad.minimize(x)
m_bad.subject_to(x >= 20, name="impossible")
m_bad.subject_to(x <= 5, name="tight_upper")

result = m_bad.solve()
if result.status == "infeasible":
    print(diagnose_infeasibility(m_bad, result))

6. Conversational Model Building (requires API key)#

discopt.chat() starts an interactive session where you describe optimization problems in natural language and the LLM builds, modifies, solves, and explains models iteratively.

import discopt

session = discopt.chat()
session.send("I need to schedule 5 workers across 7 shifts")
session.send("Each shift needs at least 2 workers")
session.send("Workers can't work more than 5 shifts")
session.send("Minimize total labor cost")
session.send("Solve it and explain the result")
session.close()

7. Natural Language Formulation (requires API key)#

from_description() uses structured tool calling to build type-safe models from natural language — no arbitrary code generation.

model = dm.from_description(
    "Minimize total shipping cost from 3 warehouses to 5 customers. "
    "Each warehouse has limited supply. Each customer demand must be met.",
    data={
        "supply": [100, 150, 200],
        "demand": [80, 60, 70, 40, 50],
        "costs": np.random.rand(3, 5) * 100,
    },
)
print(model.summary())

Provider Configuration#

discopt uses litellm for universal LLM access. Configure your provider:

Provider

Env Variable

Model String

Anthropic

ANTHROPIC_API_KEY

anthropic/claude-sonnet-4-20250514

OpenAI

OPENAI_API_KEY

openai/gpt-4o

Google

GEMINI_API_KEY

gemini/gemini-pro

Ollama

(local, no key)

ollama/llama3

# Use a specific model for any LLM feature:
result.explain(llm=True, model="openai/gpt-4o")

# Or set globally:
import os
os.environ["DISCOPT_LLM_MODEL"] = "ollama/llama3"