# PyEPO trains a predictor against the decision it produces, not the error it makes

> PyEPO is a PyTorch and JAX library for predict-then-optimize problems where a model predicts the objective coefficients and the feasible region is fixed, and its headline feature is CaVE, a cone-aligned loss for binary linear programs.

**khalil-research/PyEPO** — A PyTorch-based End-to-End Predict-then-Optimize Library for Linear and Integer Programming

- Repository: https://github.com/khalil-research/PyEPO
- Stars: 696 · Forks: 90
- Language: Jupyter Notebook
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/khalil-research-pyepo

## The problem class is narrow on purpose

PyEPO is a Python library for predict-then-optimize, and it focuses on problems where a model predicts objective coefficients and the feasible region is fixed, then trains the predictor against downstream decision quality rather than prediction error alone. That sentence is the whole scope of the project, and it is narrower than the name suggests. It is not a general machine learning library and it is not an optimizer library, it is the glue that sits between a neural network and a mathematical program solver for the specific case where the structure of the feasible region is known and only the costs are predicted. The repository is the official implementation of a paper published in Mathematical Programming Computation in 2024, by Tang and Khalil, and the citation to use is in the readme.

```
@article{tang2024,
  title={PyEPO: a PyTorch-based end-to-end predict-then-optimize library for linear and integer programming},
  author={Tang, Bo and Khalil, Elias B},
  journal={Mathematical Programming Computation},
  issn={1867-2957},
  doi={10.1007/s12532-024-00255-x},
  year={2024},
  month={July},
  publisher={Springer}
}
```

The primary language recorded for the repository is Jupyter Notebook, which is a clue about how the project is meant to be learned from.

## The solvers it can sit on top of

The optimization layer is not the project's own work, it is an adapter over existing solvers. PyEPO models optimization problems with GurobiPy, COPT, Pyomo, Google OR-Tools or MPAX, and exposes that layer through PyTorch and JAX training frontends. The last of those is the interesting integration, because MPAX is a JAX-based solver for GPU batch solving of linear and quadratic programs, and PyEPO also integrates it directly. GPU batch solving matters because the end-to-end training loop solves a problem per data point per step, and on a combinatorial instance that is the dominant cost, so moving the solver onto the accelerator is the difference between a training run that finishes and one that does not. The symbolic pyepo.dsl frontend can define linear programs, mixed integer programs and supported fixed-quadratic objective terms, and then compile the same model to a PyEPO backend, which means a model can be written once and run against different solvers.

## CaVE is the loss for binary problems

For end-to-end learning on binary linear programs, the problems that cover the travelling salesman problem, capacitated vehicle routing and knapsack, PyEPO includes CaVE, a cone-alignment loss that uses binding-constraint normals at the true optimum. The intuition behind a cone-aligned loss is that in a binary program the decision is a vertex of a polytope, and what matters for a downstream decision is the direction the objective pushes from the optimum, not the distance to it in objective value. CaVE has real requirements, though, and they are stated plainly. It requires optDatasetConstrs and a Gurobi-backed optModel for extracting binding constraints, which means the solver has to be Gurobi specifically for this loss, and the optModel has to expose the constraint data the loss needs. The reported numbers are specific as well. In the CVRP-20 setup from notebook 04, with num_data set to 1000, 10 epochs and a single process, CaVE+ trains 8.2 times faster than SPO+, and CaVE-Hybrid with solve_ratio set to 0.3 trains 10.5 times faster with a higher final regret.

## The notebooks are the tutorial

The readme links a series of notebooks that are openable directly in Colab, and they are numbered as a course. The first builds an optimization solver, the second generates synthetic data and uses optDataset, the third trains method families on a shortest path dataset, the fourth trains with the cone-aligned CaVE loss on the travelling salesman problem, the fifth visualises solutions for a two dimensional knapsack, and the sixth trains shortest path models on the Warcraft terrains dataset. That ordering matters, because each notebook assumes the objects built in the previous one, and the fourth in particular assumes a solver and a dataset that the first two create. If you are coming to the library from the paper rather than from the notebooks, the notebook sequence is the faster route, because the paper describes the method while the notebooks show the objects you actually instantiate. The repository also holds the slides from a PyEPO tutorial presented at the ACC 2024 conference.

## How the project is maintained

The badges at the top of the readme give the maintenance picture. There are separate workflows for tests and for lint, a coverage badge pointing at Coveralls, a Ruff badge for the formatter and linter, a PyPI version badge, a conda version badge through Anaconda, a downloads badge for both, and a documentation build badge. The platform badge covers Linux, macOS and Windows, and the Python versions badge is on PyPI, so the supported interpreter range is whatever PyPI records. The releases are frequent and follow a patch numbering scheme, with v2.2.7 from 2026-07-02 after v2.2.4 and v2.2.1 in June, and the last push was on 2026-10-07. The repository layout is small, with docs/ for the documentation site, notebooks/ for the tutorials, pkg/ for the package source, slides/ for the conference material, images/ for the figures including the learning framework diagram, a .pre-commit-config.yaml, and a LICENSE.

## The learning framework diagram is the mental model

The readme puts a learning framework diagram near the top, before the publication section, which is a deliberate choice about ordering. The picture is the argument in one image, and the text version of the argument is the sentence about training against downstream decision quality rather than prediction error alone. The reason that matters is the failure mode it fixes. If you train a model to predict coefficients and then feed its output to a solver, the loss on the prediction and the loss on the decision are different objectives, and the model can be improving at the first while getting worse at the second. A model that predicts a cost vector with small squared error can still produce a solution that is far from the one you wanted, because the objective is flat in the directions the solver cares about and the error sits in the directions it does not. End-to-end training closes that gap by putting the solver inside the training loop, and the loss function then has to be one the solver's geometry supports, which is exactly what the cone alignment in CaVE is for.

## Where this is the wrong choice

Three limits are worth naming. The first is that a solver in the training loop is expensive, and the readme's own speed comparisons are measured against a specific setup, the CVRP-20 case with a thousand data points over ten epochs on a single process, so a reader should not generalise the ratio to a different instance family. The second is the Gurobi requirement for CaVE, which is a commercial solver, so a team that standardised on an open source solver gets the rest of the library but not the flagship loss, and the alternatives in the list are COPT, OR-Tools, Pyomo and MPAX. The third is that the scope excludes the other half of predict-then-optimize, namely the case where the structure of the feasible region is itself learned or produced by a model, because here the feasible region is fixed and only the coefficients move. That is a sensible boundary for a library that wants to be useful, and it does mean the tool covers one of several ways to build these systems rather than all of them.

## Conclusion

PyEPO suits a team building a model whose output is fed straight into an optimizer, and it suits anyone who wants to train against downstream decision quality rather than prediction error, particularly on a binary linear program. It is the wrong tool if the decision variables themselves are what you are predicting, since the library is scoped to the case where the feasible region is fixed and only the objective coefficients are learned. It is also the wrong choice if you cannot run a MILP solver during training, because the training loop calls the solver, and CaVE additionally needs a Gurobi-backed model to extract binding constraints. Before adopting it, read the notebooks in order, since the first three build the solver, the dataset and the training loop that the later ones assume, and note the release cadence, with v2.2.7 from 2026-07-02 and the last push on 2026-10-07.

## FAQ

### What problem class does PyEPO target?

It focuses on predict-then-optimize problems where a model predicts objective coefficients and the feasible region is fixed, and it trains the predictor against downstream decision quality rather than prediction error alone. The optimization layer is modelled with GurobiPy, COPT, Pyomo, Google OR-Tools or MPAX and exposed through PyTorch and JAX frontends.

### What is the CaVE loss and what does it need?

CaVE is a cone-alignment loss for binary linear programs that uses binding-constraint normals at the true optimum, so training follows the direction of the decision rather than the distance in objective value. It requires optDatasetConstrs and a Gurobi-backed optModel to extract binding constraints, and in the CVRP-20 setup with a thousand data points over ten epochs on one process, CaVE+ trains 8.2 times faster than SPO+ while CaVE-Hybrid with solve_ratio 0.3 trains 10.5 times faster.

### How do I learn PyEPO?

The repository ships a numbered series of notebooks that open in Colab. The first builds an optimization solver, the second generates synthetic data and uses optDataset, the third trains on a shortest path dataset, the fourth covers the CaVE loss on the travelling salesman problem, the fifth visualises a two dimensional knapsack and the sixth trains on the Warcraft terrains dataset. The official documentation is hosted at the project site and the slides from an ACC 2024 tutorial are in the slides directory.

## Sources

- [Issues](https://github.com/khalil-research/PyEPO/issues)
- [khalil-research/PyEPO on GitHub](https://github.com/khalil-research/PyEPO)
- [License: MIT](https://github.com/khalil-research/PyEPO/blob/main/LICENSE)
- [README](https://github.com/khalil-research/PyEPO/blob/main/README.md)
- [Releases](https://github.com/khalil-research/PyEPO/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/khalil-research-pyepo
