Metadata-Version: 2.4
Name: optimaze
Version: 0.4.0
Summary: AI agent that auto-tunes Gurobi MIP solver runs via parameter tuning + formulation rewriting, with a soft-prompt-specialized proposer trained on MIPLIB.
Project-URL: Homepage, https://github.com/danielpuri1901/optimaze-agent
Project-URL: Documentation, https://github.com/danielpuri1901/optimaze-agent#readme
Project-URL: Repository, https://github.com/danielpuri1901/optimaze-agent
Project-URL: Issues, https://github.com/danielpuri1901/optimaze-agent/issues
Author-email: Daniel Puri <danielpuri1901@gmail.com>
License: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.10
Requires-Dist: faiss-cpu>=1.7.4
Requires-Dist: gurobipy>=11.0.0
Requires-Dist: networkx>=3.0
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: pyarrow>=14.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: scipy>=1.10
Requires-Dist: torch>=2.1
Requires-Dist: transformers>=4.40
Requires-Dist: typer>=0.12.0
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.9; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest-mock>=3.12; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: gpu
Requires-Dist: faiss-gpu>=1.7.4; extra == 'gpu'
Provides-Extra: hosted-llm
Requires-Dist: anthropic>=0.25; extra == 'hosted-llm'
Requires-Dist: openai>=1.20; extra == 'hosted-llm'
Provides-Extra: train
Requires-Dist: accelerate>=0.27; extra == 'train'
Requires-Dist: bitsandbytes>=0.42; (platform_system != 'Darwin') and extra == 'train'
Requires-Dist: datasets>=2.18; extra == 'train'
Requires-Dist: peft>=0.10; extra == 'train'
Description-Content-Type: text/markdown

# optimaze

**Auto-tune Gurobi MIP solver runs with a multi-agent bandit + retrieval system.**

[![PyPI](https://img.shields.io/pypi/v/optimaze)](https://pypi.org/project/optimaze/)
![Python](https://img.shields.io/badge/python-3.10+-blue)
![License](https://img.shields.io/badge/license-MIT-green)

## What this is

`optimaze` is a multi-agent system that auto-tunes Gurobi MIP solver parameters
on a per-instance basis. It combines a Thompson-sampling bandit over a 9-parameter
config space, k-NN retrieval over a MIPLIB instance index (24-dimensional
structural features), and an optional LLM proposer that injects creative
out-of-distribution arms. Trials run in a pool of warm Gurobi worker
subprocesses, and each run emits an auditable report explaining the winning
config.

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

## Install

```bash
pip install optimaze
```

Optional extras:

```bash
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

```bash
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):

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

Library use:

```python
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.1.

`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.1)
```

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.1. 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.1 differentiator.

## Roadmap

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

- **v0.4.1** — 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.5** — `formulation_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:

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

## License

MIT. See [LICENSE](LICENSE).
