Skip to content

Repository files navigation

 MIP++

One model, any solver — at raw C API speed.

MIP++ is a header-only, dependency-free C++23 library for linear, mixed-integer, and quadratic programming. It gives you an algebraic modeling syntax as readable as JuMP or Pyomo, but compiles down to direct calls into the solver's own C API — no intermediate model representation, no extraction step, no allocation in the expression layer. The same model code targets any of 11 solvers; you choose the backend at compile time, and its shared library is discovered and loaded dynamically at runtime, with no link-time solver dependency.

📖 Documentation — start with the Getting Started guide.

C++23 Conan License

A first model

#include <print>

#include "mippp/solvers/highs/all.hpp"

using namespace mippp;
using namespace mippp::operators;

int main() {
    highs_lp model;  // loads HiGHS at runtime, swap for gurobi_*, cbc_*, ...

    auto x1 = model.add_variable();
    auto x2 = model.add_variable({.upper_bound = 3});

    model.set_maximization();
    model.set_objective(4 * x1 + 5 * x2);
    model.add_constraint(x1 <= 4);
    model.add_constraint(2 * x1 + x2 <= 9);

    model.solve();
    auto sol = model.get_solution();

    std::println("objective = {}", model.get_solution_value());
    std::println("x1 = {}, x2 = {}", sol[x1], sol[x2]);
    return 0;
}

The Getting Started guide walks through this model, the expression system, and solver selection. Runnable examples — N-Queens, sudoku, TSP with lazy subtour elimination, cutting stock via column generation, an infeasible transportation plan diagnosed through its IIS — live in examples/.

Why MIP++

One model, any solver

Benchmarking Gurobi vs. CPLEX vs. HiGHS vs. SCIP is a two-line change and a recompile — no #ifdef soup, no linking against a solver SDK. Solver shared libraries are discovered at runtime via exact path parameter, per-solver environment variables (MIPPP_HIGHS_LIBRARY, etc.) or LD_LIBRARY_PATH and system directories: a single compiled binary runs on whatever solver the machine has installed. (Why MIP++)

No modeling tax

Filling a model through MIP++ costs 102–108% of what calling the solver's own C API costs. Against the other modeling layers, on the same backend: 1.2–5.6× faster than OR-Tools' two C++ APIs, 3.7–18× faster than JuMP, and one to two orders of magnitude faster than the Python interfaces — all measured on model construction alone, never on solving. Build time is noise for a one-shot solve that runs for hours, but can dominate in algorithms that build, modify, and re-solve constantly: column generation, Benders decomposition, cutting planes, large-scale experiments. MIP++ is built for those. See the benchmark ↓

The speedup comes from the expression system's architecture: objectives and constraints are composed from C++ ranges as lazy views using xsum, and they allocate nothing. When a constraint is added, its term range is iterated directly into the pre-allocated scratch buffers passed to the solver's C API (Highs_addRow, GRBaddconstr, etc.). There is no intermediate model representation to build, extract, or garbage-collect. (Expressions and constraints)

Built for algorithms, not just models

A MIP++ model is the solver's native model. There is no extraction step, so re-solves after modifications (adding variables, changing bounds, adding constraints) pay only the solver's incremental update cost, never a full model rebuild.

The library exposes the algorithmic hooks that decomposition and cutting-plane methods need:

  • Branch-and-cut callbacks with lazy constraints — the TSP example in examples/travelling_salesman_dfj/ adds subtour elimination cuts through a typed callback handle using the same xsum syntax as the main model.
  • Column generation via add_column, dual values (get_dual_solution), and reduced costs (get_reduced_costs). A full column_manager framework tracks columns across pool and master states, propagates pricing events to per-column properties (reduced cost, primal value, age) through compile-time event dispatch, and provides pluggable activation/eviction strategies — all at zero runtime overhead for unused properties.
  • MIP starts, indicator constraints, and in-place model updates (bound changes, coefficient changes, variable removal). When columns are evicted, solvers compact their internal arrays, invalidating external indices. MIP++ keeps user-facing variable handles stable through a bidirectional handle/native-ID map — but the map is only allocated on the first deletion; models that never remove variables pay nothing beyond a branch prediction.

Solve statuses that survive the backend swap

Solver-agnostic code usually hides backend-specific outcomes behind a lowest-common-denominator enum. MIP++ does the opposite: each backend's get_status() returns a std::variant whose alternatives are exactly the statuses that solver actually reports. MOSEK's LP variant distinguishes primal_and_dual_infeasible from plain infeasible; Clp's carries nothing beyond unknown (the pre-solve state every backend starts in), optimal, infeasible, and unbounded. Nothing is erased.

Generic queries work through the status type hierarchy: primal_and_dual_infeasible inherits from infeasible, which inherits from infeasible_or_unbounded. Calling is_a<status::infeasible_or_unbounded> matches any of them — one question, every solver. Calling is<status::primal_and_dual_infeasible> asks the exact question instead, and will only compile if the backend can report it. This abstraction is free at runtime — the inheritance check is resolved at compile time, so the compiler reduces it to a direct index check. (Status and limits)

Performance

Time to build the N-Queens model (N² binary variables, 6N−6 constraints) through eight modeling libraries across C++, Julia and Python. Only model construction is timed, never the resolution: the timer stops once the model holds every variable and constraint, after flushing any pending update.

MIP++ vs. the Gurobi C API

The most direct measure of modeling overhead — the raw C API in milliseconds, MIP++ as a percentage of it, the other Gurobi-capable interfaces as multiples:

N Gurobi C API MIP++ gurobipy JuMP direct Python-MIP
100 3.2 ms 102% 6.5× 3.5× 19.4×
500 48.2 ms 104% 9.6× 7.5× 17.8×
1000 190.0 ms 107% 12.3× 7.4× 18.5×

MIP++ stays within 2–8% of the raw C API across the whole sweep: the abstraction layer is thin. Handing Gurobi the entire matrix in one GRBaddconstrs call rather than one GRBaddconstr per constraint is worth nothing, so matching the per-constraint path is the meaningful comparison rather than a handicap.

MIP++ vs. the other C++/Julia interfaces

MIP++ in absolute milliseconds, the other interfaces as multiples of it on the same backend (HiGHS, the one backend all of them support):

N MIP++ OR-Tools MPSolver OR-Tools MathOpt JuMP cached JuMP direct
100 1.6 ms 1.3× 2.6× 3.7× 12.0×
500 37.4 ms 1.3× 3.7× 6.1× 16.1×
1000 151.5 ms 1.3× 5.6× 5.7× 18.3×

Both OR-Tools APIs are benchmarked in their fastest row-filling form — coefficients written straight into an opened row, worth ~1.8× for MPSolver and ~1.2× for MathOpt over the idiomatic expression object — so OR-Tools is shown at its best. On Cbc the gap against MPSolver is wider than on HiGHS: 2.4–3.0× across the sweep, MIP++ filling the model in 66.7 ms at N=1000.

Note

The comparison is not work-for-work, and the bias is against MIP++. OR-Tools' two APIs and JuMP's default Model accumulate the model in their own structures and hand it to the solver later; MIP++, the Gurobi C API and JuMP's direct_model write into the solver's own model as each constraint is added. Two tells: JuMP cached fills at the same speed whichever backend is named (867 ms HiGHS, 873 ms Gurobi at N=1000), and MPSolver needs ~200–220 ms at N=1000 for Cbc, HiGHS and SCIP alike. This is also why OR-Tools appears faster on SCIP (45% of MIP++) — it has not handed SCIP anything yet.

Against the Python interfaces on the same backend, MIP++ is 11× faster than gurobipy, 54× than Python-MIP, 103× than PuLP and 128× than highspy.

Warning

The Cbc figures were measured against Cbc's devel branch, the only version that caches addRow; release 2.10.13 (what coinor-libcbc-dev ships) flushes the matrix on every call and is substantially slower for every interface that builds directly in Cbc.

N-Queens is a variable-heavy, constraint-light model, measured on a single machine and compiler — the ratios transfer, the absolute times do not.

Full tables (N=100–1000 in steps of 100), per-backend results for eight solvers, build-variant comparisons, limitations, and reproduction instructions: Performance — benchmark code in mippp_nqueens.

Supported solvers

Backend LP MILP QP
HiGHS ✓ ✓ ✓
Gurobi, CPLEX, Xpress, COPT, MOSEK, GLPK ✓ ✓
SCIP ✓
Clp, SoPlex ✓
Cbc (experimental) ✓

Per-feature support (duals, reduced costs, callbacks, MIP starts, indicator constraints, variable removal, IIS, …) varies by backend — see the feature matrices in Choosing a solver.

Is MIP++ for you?

MIP++ has a deliberate niche. It is worth being honest about where it fits and where it doesn't.

Where MIP++ fits well

Optimization embedded in a larger C++ system. A simulator, a planning service, a shipped binary that must run with whatever solver the user has installed. The runtime dlopen-based solver loading means no recompilation per solver and no link-time SDK dependency — this is the sweet spot.

Cross-solver experiments. Reviewers asking for results on Gurobi and an open solver, licenses that differ between your laptop, the cluster, and your coauthors' machines. The two-line backend switch was built for exactly this.

Build-bound iterative methods. Column generation, cutting planes, Benders decomposition, iterated reoptimization. In-place model updates, add_column with a full column pool manager, and lazy-constraint callbacks are here today.

Where something else is a better fit

Everyday modeling in Python or Julia. Stay with gurobipy, JuMP, or Pyomo. They are mature, their communities are large, and for a one-shot solve where solver time dominates, the modeling overhead rarely matters.

Heavy solver-specific parameter tuning. There is no uniform interface for solver-specific knobs. They stay reachable — model.native_model() hands out the solver's own handles and the *_api objects expose the raw C entry points — but each such call is backend-specific code.

Constraint programming or scheduling. Use OR-Tools CP-SAT or a dedicated CP solver.

Important

MIP++ requires GCC 14+ / C++23 (GCC 15 / C++26 recommended) and assumes comfort with modern C++ — ranges, concepts, and template diagnostics. It is a young, single-maintainer project; contributions are welcome, but pin a version if you build long-lived research code on it.

Installation

MIP++ is header-only and has no library dependency — solver libraries are opened at runtime through the platform loader (dlopen / LoadLibrary), which MIP++ wraps itself. Install via Conan, as a CMake subdirectory, or with a plain CMake install:

git clone https://github.com/fhamonic/mippp && cd mippp
conan create . -u -b=missing -pr=<your_conan_profile>        # Conan
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=<prefix> && cmake --install build   # CMake package

Solver shared libraries are discovered at runtime; only the solvers you actually have installed need to be present, and nothing solver-related is needed at compile time. Per-solver environment variables (MIPPP_HIGHS_LIBRARY, MIPPP_GUROBI_LIBRARY, etc.) can pin specific library paths or versioned sonames. Full instructions: Installation.

Roadmap

The modeling core is in place: LP/MILP/QP, lazy-constraint callbacks, column generation with a pool manager, reduced costs, MIP starts, indicator constraints, in-place model updates, infeasibility diagnosis by irreducible infeasible subsystem (IIS), through the solver's own routine on Gurobi, CPLEX, Xpress, COPT and HiGHS and a deletion filter on every backend.

Planned, roughly by priority:

Priority Feature Notes
🔴 LP basis warm-starts Concepts specified (has_lp_basis, has_lp_basis_warm_start); no backend implements get_basis/set_basis yet
🔴 User-cut callbacks For cutting-plane methods at node relaxations
🔴 Heuristic-solution injection Injecting primal solutions from within callbacks
🟠 SOS1/SOS2 constraints Concepts specified (has_sos1_constraints, has_sos2_constraints); no backend implements add_sos1_constraint/add_sos2_constraint yet
🟠 QP objectives beyond HiGHS Extend Hessian support to Gurobi, CPLEX, MOSEK, etc.
🟡 QCP/SOCP constraints Quadratically constrained programs
🟡 Model file I/O Read/write LP and MPS files
🟡 MILP fuzzy tests Differential fuzzing of the MILP interface against a reference implementation, as lp_fuzzy_tests does for LPs; the hand-written suites left gaps in the backends
⚪ Solution pools, multi-objective, semi-continuous variables, progress getters

Note

Since a MIP++ model is the solver's native model, re-solves after in-place modifications (adding rows, changing bounds) almost always warm-start from the last basis implicitly. The roadmap item above is about explicit basis get/set — transferring a basis between models or storing one for later.

The first three items are what build-bound, re-solve-heavy research code wants most. Until they land, a column-generation or cutting-plane study hitting those specific features may still be better served by a direct solver API — and the honest comparison is in the Is MIP++ for you? section above.

Contributions are welcome — see CONTRIBUTING.md, and open an issue to claim an item.

Acknowledgements

MIP++ is grounded in the PhD thesis and postdoctoral positions of François Hamonic, funded by Région Sud - Provence-Alpes-Côte d'Azur, Natural Solutions, the European Research Council grant SCALED to Cécile ALBERT (ERC-STG no. 949812), the ANR project RESILIENCE (no. ANR-24-PEVD-0002) and the project OASIS of ITEM, an A*Midex Initiative d'Excellence institute funded under France 2030 (AMX-19-IET-012).

Documentation, citing, license

About

Header-only C++23 library for linear, mixed-integer and quadratic programming — one model, any solver, at raw C API speed.

Topics

Resources

Code of conduct

Contributing

Stars

17 stars

Watchers

2 watching

Forks

Releases

Contributors

Languages