MIP++ (aka MIPpp) is a header‑only C++ library that brings a Pythonic modeling interface to mixed‑integer linear programming. It lets you load multiple solver backends (Gurobi, Cbc, Highs, etc...) at runtime, and uses template metaprogramming to preserve Python‑like syntactic sugar while emitting near‑optimal code at compile time.
Work in progress.
- Header-only: Dynamically loading the C API of solvers allows MIP++ to stay header-only and easy to integrate to C++ projects.
- Modern C++20: Utilizes ranges, lambdas, and template metaprogramming for an elegant and efficient API allowing a Pythonic syntax.
- Solver Agnostic: Supports a variety of solvers (Gurobi, CPLEX, Clp, Cbc, Highs, SoPlex, SCIP, MOSEK, COPT, GLPK and more upcoming) with a unified API.
- High Performance: Optimized for speed with zero cost abstractions and no unnecessary data copying.
git clone
cd mippp
conan create . -u -b=missing -pr=<your_conan_profile>
Then add mippp/<version>
as a requirement in your conanfile.txt or conanfile.py.
Simply add mippp/1.0.0
to your conanfile.txt
or conanfile.py
.
cd <your_project> && mkdir dependencies
git submodule add https://github.com/fhamonic/mippp dependencies/mippp
Then in your CMakeLists.txt:
add_subdirectory(dependencies/mippp)
...
target_link_libraries(<some_target> INTERFACE mippp)
And ensure that your CMake can find the Range-v3 and dylib libraries with find_package
calls.
#include "mippp/mip_model.hpp"
using namespace fhamonic::mippp;
using namespace fhamonic::mippp::operators;
...
gurobi_api api("gurobi120"); // loads libgurobi120.so C API
gurobi_lp model(api);
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::cout << std::format("x1: {}, x2: {}", sol[x1], sol[x2]) << std::endl;
Using the MELON library, we can express the Maximum Flow problem as
#include "melon/static_digraph.hpp"
using namespace fhamonic::melon;
...
static_graph graph = ...;
arc_map_t<static_graph, double> capacity_map = ...;
vertex_t<static_graph> s = ...;
vertex_t<static_graph> t = ...;
...
gurobi_api api();
gurobi_lp model(api);
auto F = model.add_variable();
auto X_vars = model.add_variables(graph.num_arcs());
model.set_maximization();
model.set_objective(F);
auto flow_conservation_constrs = model.add_constraints(
graph.vertices(),
[&](auto && u) {
return OPT((u == s), xsum(graph.out_arcs(s), X_vars) ==
xsum(graph.in_arcs(s), X_vars) + F);
},
[&](auto && u) {
return OPT((u == t), xsum(graph.out_arcs(t), X_vars) ==
xsum(graph.in_arcs(t), X_vars) - F);
},
[&](auto && u) {
return xsum(graph.out_arcs(u), X_vars) ==
xsum(graph.in_arcs(u), X_vars);
});
for(auto && a : graph.arcs()) {
model.set_variable_upper_bound(X_vars(a), capacity_map[a]);
}
static_graph graph = ...;
arc_map_t<static_graph, double> length_map = ...;
vertex_t<static_graph> s = ...;
vertex_t<static_graph> t = ...;
...
gurobi_api api();
gurobi_lp model(api);
auto X_vars = model.add_variables(graph.num_arcs(),
[](arc_t<static_graph> a) -> std::size_t { return a; });
model.set_maximization();
model.set_objective(xsum(graph.arcs(), [&](auto && a){
return length_map[a] * X_vars(a);
}));
for(auto && u : graph.vertices()) {
const double extra_flow = (u == s ? 1 : (u == t ? -1 : 0));
model.add_constraint(xsum(graph.out_arcs(u), X_vars) ==
xsum(graph.in_arcs(u), X_vars) + extra_flow);
}
auto indices = ranges::views::iota(0, 9);
auto values = ranges::views::iota(1, 10);
auto coords = ranges::views::cartesian_product(ranges::views::iota(0, 3),
ranges::views::iota(0, 3));
gurobi_api api;
gurobi_milp model(api);
auto X_vars =
model.add_binary_variables(9 * 9 * 9, [](int i, int j, int value) {
return (81 * i) + (9 * j) + (value - 1);
});
auto single_value_constrs = model.add_constraints(
ranges::views::cartesian_product(indices, indices), [&](auto && p) {
auto && [i, j] = p;
return xsum(values, [&](auto && v) { return X_vars(i, j, v); }) == 1;
});
auto one_per_row_constrs = model.add_constraints(
ranges::views::cartesian_product(values, indices), [&](auto && p) {
auto && [v, i] = p;
return xsum(indices, [&](auto && j) { return X_vars(i, j, v); }) == 1;
});
auto one_per_col_constrs = model.add_constraints(
ranges::views::cartesian_product(values, indices), [&](auto && p) {
auto && [v, j] = p;
return xsum(indices, [&](auto && i) { return X_vars(i, j, v); }) == 1;
});
auto one_per_block_constrs = model.add_constraints(
ranges::views::cartesian_product(values, coords), [&](auto && p) {
auto && [v, b] = p;
return xsum(coords, [&](auto && p2) {
return X_vars(3 * std::get<0>(b) + std::get<0>(p2),
3 * std::get<1>(b) + std::get<1>(p2), v);
}) == 1;
});
for(auto [i, j, value] : grid_hints) {
model.set_variable_lower_bound(X_vars(i, j, value), 1);
}
model.solve();
auto solution = model.get_solution();
for(auto i : indices) {
for(auto j : indices) {
for(auto v : values) {
if(solution[X_vars(i, j, v)]) std::cout << ' ' << v;
}
}
std::cout << std::endl;
}
- Add callbacks for Highs, CPLEX, Cbc, MOSEK and COPT.
- Add support for XPRS
- Add special constraints (sos and indicator constraints)
- Add column generation features