Skip to main content

AI agent that auto-tunes Gurobi MIP solver runs via parameter tuning + formulation rewriting, with a soft-prompt-specialized proposer trained on MIPLIB.

Project description

optimaze

A multi-agent system that auto-tunes Gurobi MIP solver runs. A central orchestrator runs the optimization loop, coordinating specialized agents that propose a solver config, run it, and diff the result — converging on a faster Gurobi configuration for each model.

PyPI Python License

What this is

optimaze wraps Gurobi in an agentic tuning loop. A central orchestrator runs the optimization loop and coordinates three specialized agents:

  • Proposer (tuning-strategy) — decides the next solver config to try. It runs a Thompson-sampling bandit over a 9-parameter space (MIPFocus, presolve, cuts, symmetry breaking, and more), seeded by k-NN retrieval over structurally similar MIPLIB instances, with an optional LLM that injects creative out-of-distribution configs.
  • Solver runner — executes each proposed config against the model in a pool of warm Gurobi worker subprocesses and records the outcome: solve time, optimality gap, and branch-and-bound nodes.
  • Performance differ — compares every run to the default-config baseline, updates the proposer's beliefs, and writes an auditable report explaining why the winning config won.

The orchestrator loops until the time budget is spent or progress stalls. An automated eval harness scores runs against the MIPLIB benchmark suite, and every run emits structured logs plus a markdown report from day one — so you can always see what each agent did and why.

Compared to grbtune (Gurobi's built-in exhaustive tuner), optimaze reads the model: it uses constraint-graph features and priors from structurally similar instances rather than starting from scratch.

Install

pip install optimaze

Optional extras:

pip install 'optimaze[hosted-llm]'   # Anthropic / OpenAI backends
pip install 'optimaze[train]'        # PEFT, datasets, accelerate (offline soft-prompt training)
pip install 'optimaze[dev]'          # pytest, ruff, mypy

You'll need a working Gurobi license on the machine that runs trials — Gurobi runs locally and stays under your license.

Quickstart

optimaze tune model.mps

Defaults: 600 s wall-clock budget, up to 50 trials, 4 parallel workers, output written to ./optimaze_out/. Use --no-llm for a pure-bandit run (no model download, no API key needed):

optimaze tune model.mps --no-llm --budget 60 --max-trials 10 --seed 42

Library use:

from pathlib import Path
from optimaze.config import OptimazeConfig
from optimaze.core.orchestrator import Orchestrator

orchestrator = Orchestrator(config=OptimazeConfig(output_dir=Path("./optimaze_out")))
result = orchestrator.tune(Path("model.mps"), budget_seconds=60, max_trials=10, seed=42)
print(f"best solve_time: {result.best_outcome.solve_time_s:.3f}s "
      f"({result.improvement_pct:.2f}% improvement)")

Example output

The following is the real output of a smoke-test run on the MIPLIB instance markshare_4_0 (dev-tier, 4-trial budget, no LLM, seed 42). Verbatim from optimaze_out/tuning_report.md:

# optimaze tuning report — markshare_4_0

- trials: 4
- baseline solve_time: 0.308s
- best solve_time: 0.236s
- improvement: 23.32%

## Best config
- `BranchDir` = 0
- `Cuts` = -1
- `Heuristics` = 0.6438651200806645
- `MIPFocus` = 1
- `Method` = 3
- `NodeMethod` = 1
- `Presolve` = 2
- `Symmetry` = 0
- `VarBranch` = 3

## Rationale
Best config was 1.19x faster than runner-up (0.24s vs 0.28s) because
Heuristics=0.6438651200806645 (vs 0.05), MIPFocus=1 (vs 0), Method=3 (vs -1),
Presolve=2 (vs -1), Symmetry=0 (vs -1), VarBranch=3 (vs -1).

## Trial log
1. 0.308s  status=2  Baseline: Gurobi default config (always tried first).
2. 0.236s  status=2  Thompson sample: arm pulled 0x, posterior Beta(α=1.00, β=1.00).
3. 0.281s  status=2  Thompson sample: arm pulled 1x, posterior Beta(α=1.20, β=1.80).
4. 0.421s  status=2  Thompson sample: arm pulled 0x, posterior Beta(α=1.00, β=1.00).

Headline: 23.32% wall-clock improvement on markshare_4_0 with 4 trials (0.308s → 0.236s). This is one instance, not a benchmark sweep; broader ML-eval numbers will land alongside the trained soft prefix in v0.4.2.

optimaze_out/ also contains:

  • best_config.json — the winning parameter set as JSON
  • trials.jsonl — one line per trial with config, hash, status, gap, nodes
  • logs/ — per-trial Gurobi logs

How it works

Online (optimaze tune)

user model (.mps / .mps.gz)
    │
    ▼
InstanceEmbedder ──► 24-d feature vector
    │
    ▼
InstanceIndex (faiss k-NN) ──► priors from similar historical instances
    │
    ▼
BanditProposer (Thompson sampling + optional LLM rationale)
    │
    ▼
TrialPool (warm Gurobi worker subprocesses)
    │
    ▼
PerfDiffer ──► updates bandit posterior, generates explanation
    │
    └──► loop until budget exhausted or 5 trials w/o improvement
    │
    ▼
Emit: best_config.json, tuning_report.md, trials.jsonl
Optional --repo: GitCommitter opens branch + PR

Per-arm posteriors are Beta over a normalized speedup r = clip(baseline_time / trial_time, 0, 5) / 5; the proposer Thompson-samples one θ per arm and picks the max. The default Gurobi config is always tried first as the baseline arm.

Offline (training pipeline, not run by users)

MIPLIB instances (240 catalogued; tiers: dev=10 / ci=50 / benchmark=180)
    │
    ▼
ActiveLearningHarness (Bayesian acquisition picks informative trials)
    │
    ├─► TrialPool ──► trial_outcomes.parquet
    └─► InstanceEmbedder ──► instance_index.faiss
            │
            ▼
        DatasetBuilder (outcomes → (features → config + rationale) pairs;
                         rationales labelled by Claude/GPT at training time)
            │
            ▼
        SoftPromptTrainer (PEFT prefix tuning over Qwen2.5-7B, ~20 tokens)
            │
            ▼
        optimaze_prefix.pt   (lands in v0.4.2)

The offline pipeline ships in the wheel under optimaze.harness and optimaze.training. Users can re-run it on their own trial data, but the shipped artifacts come from our training runs.

LLM backends

Backend Flag Soft prefix Network Notes
Qwen local --llm qwen-local (default) yes (when shipped) none Loads Qwen/Qwen2.5-7B-Instruct via transformers. CPU works; GPU recommended once the trained prefix lands.
Anthropic --llm anthropic no (hard-prompt) yes Requires ANTHROPIC_API_KEY. Install with pip install 'optimaze[hosted-llm]'.
OpenAI --llm openai no (hard-prompt) yes Requires OPENAI_API_KEY. Install with pip install 'optimaze[hosted-llm]'.
Pure bandit --no-llm n/a none No model load, no API key. The example above used this mode.

If an LLM backend fails to initialise (missing API key, no model cache, etc.), the CLI logs a warning and falls back to pure-bandit Thompson sampling. The bandit alone is enough to produce the 23.32% improvement shown above.

Differentiator vs grbtune

  • Reads the model. Structural features (sparsity, integrality, constraint graph) inform config proposals. grbtune is black-box exhaustive search.
  • Cold-start from neighbours. k-NN over the MIPLIB index reuses configs that worked on structurally similar instances rather than starting from the default every time.
  • Auditable. Every run emits a markdown report explaining the winning config relative to the baseline and the runner-up; optional --repo opens a PR with the diff.

Status / v0.4.0 notes

This is the first release of the v2 rewrite. Honest scope:

  • Bandit + retrieval pipeline: complete and shipping. The 23.32% number above used this path with the LLM disabled.
  • Hosted LLM backends (Anthropic, OpenAI): complete; use hard-prompt templates. Production-ready as creative proposers.
  • Trained soft-prompt artifact (optimaze_prefix.pt): not yet shipped. The training code (optimaze.training) is included but the artifact lands in v0.4.2. v0.4.0 ships with a hard-prompt fallback in BanditProposer and QwenLocalClient that works without a GPU and without the prefix file.
  • Formulation rewriting (.py input): formulation/rewriter.py is in-tree but the CLI path is param-only in v0.4.0. v0.5 is when the four rewrite types (aggregate constraints, eliminate redundant vars, symmetry-breaking, bound tightening) get plumbed through optimaze tune.

If you're evaluating optimaze against grbtune: the differentiator at this version is cold-start + retrieval + auditable rationales. Soft-prompt specialisation is the v0.4.2 differentiator.

Roadmap

See the full design spec at docs/superpowers/specs/2026-05-17-optimaze-v2-design.md, section 15 (out of scope for v1) and section 18 (build order).

  • v0.4.2 — Trained soft-prompt artifact (optimaze_prefix.pt) shipped in the wheel. ML-eval reports (≥60% of ci-tier instances ≥10% faster vs. default). Soft-prompt ablation numbers.
  • v0.5formulation_trial plumbed through CLI: four named rewrite types (aggregate constraints, eliminate redundant vars, symmetry-breaking, tighten bounds). Param interaction modelling.
  • Later — LoRA fine-tune swap-in once the harness has ≥1k labelled trials. HuggingFace Hub mirror of the prefix artifact. GNN constraint-graph embeddings.

Citation

If you use optimaze in academic work, please cite:

@software{puri_optimaze_2026,
  author  = {Puri, Daniel},
  title   = {optimaze: multi-agent auto-tuning for Gurobi MIP solvers},
  year    = {2026},
  version = {0.4.1},
  url     = {https://github.com/danielpuri1901/optimaze-agent}
}

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

optimaze-0.4.1.tar.gz (131.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

optimaze-0.4.1-py3-none-any.whl (77.5 kB view details)

Uploaded Python 3

File details

Details for the file optimaze-0.4.1.tar.gz.

File metadata

  • Download URL: optimaze-0.4.1.tar.gz
  • Upload date:
  • Size: 131.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for optimaze-0.4.1.tar.gz
Algorithm Hash digest
SHA256 e0fe515baa86ba8067e53cf2bb5c2e21081c6322e129bb89fb76640f527bebe7
MD5 c23f41fc07c7010559cd9006d7cbab53
BLAKE2b-256 73948ce058484387247ff34493079048fc5d1117b3e5b0cf420554f77cc622e4

See more details on using hashes here.

File details

Details for the file optimaze-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: optimaze-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 77.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for optimaze-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3f3e66eb6ad3d6b62fa4aeec6011a97ac927fbe829f3ce7d593a2d7c451d9efb
MD5 0276d5c10a94f2c2c1dee5f2c7302a44
BLAKE2b-256 189378412f1bd0df3497258ed0bd32adfc5579e3ed7bccbf3de9a9c7a50ab730

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page