Header-only C++20 library for Hypervolume Scalarization in the Multiobjective Knapsack Problem (MOKP).
In multiobjective optimization we seek solutions that are Pareto-optimal — no objective can be improved without degrading another. The hypervolume indicator measures the volume of objective space that is dominated by a set of solutions and bounded by a reference point.
Hypervolume Scalarization (HS) turns this set-level indicator into a
scalar objective for a single solution. Given a set of reference points
HS-MOCO encodes this objective as a Mixed-Integer Program (MIP) specifically parameterized for the Multiobjective Knapsack Problem (MOKP), and provides three alternative formulation strategies, trading off model size and numerical behaviour directly tailored for Gurobi formulation patterns. See the Strategies section below for details.
Note
Solver backend. This library requires Gurobi Optimizer
to function, directly relying on its C++ API capabilities to interpret linear
constraints (MILP), quadratic constraints (MIQP), and non-linear function
features (FuncNonLinear) like logs and exponential operations utilized in the formulations.
Note
Maximization only. The HS formulation as implemented assumes a
maximization problem (all objectives are to be maximized). For
minimization problems, this can be easily adapted by negating the
objective values, i.e., replacing
The most conceptually simple strategy. Each product
Takes the logarithm of the HS product, converting products into sums:
For
For
Each
Expands the product
Since
Tip
Each strategy header file
(miqp.hpp,
log.hpp,
milp.hpp)
contains detailed documentation on the mathematical formulation,
model size analysis, and implementation steps. Refer to the file-level
docstrings for a comprehensive deep-dive beyond the overview above.
- Built exclusively on top of the Gurobi C++ API.
- Three MIP formulation strategies for MOKP instances:
- MIQP (bilinear product)
- Logarithm transformation and Log-Sum-Exp (LSE) reformulation
- MILP (McCormick linearization)
- Build once, solve many times: separate model construction from solving, allowing efficient re-use across multiple reference-point sets
- Pure header-only: no compiled library, just
#include <hs_moco/hs_moco.hpp>
| Dependency | Version | Notes |
|---|---|---|
| C++ compiler | C++20 support | GCC ≥ 10, Clang ≥ 13, Microsoft Visual C++ ≥ 19.29 |
| CMake | ≥ 3.16 | |
| Gurobi Optimizer | ≥ 9.5 | Set GUROBI_HOME to your installation directory |
Set GUROBI_HOME to your Gurobi installation directory (e.g. /opt/gurobi1203/linux64).
Note that: Example programs (and their dependencies) are configured and built only when hs-moco is the top-level project. If used as a subproject (e.g., via add_subdirectory or FetchContent), examples are skipped by default.
| Dependency | Version | Notes |
|---|---|---|
mooutils |
latest | Needed only for examples; auto-fetched via FetchContent if not found |
# Configure (uses CMake presets)
cmake --preset release
# Build
cmake --build --preset release
# Or manually:
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .The example executable example_mokp will be in build/release/examples/.
The MOKP example uses MOKP instances from
gaplopes/mobkp-instances,
which are downloaded automatically via CMake FetchContent at configure time.
No manual setup is required. You can also provide any compatible .in instance
file.
# Use the bundled default instance (max_k = 5)
./build/release/examples/example_mokp
# Use a custom instance file
./build/release/examples/example_mokp /path/to/instance.in
# Use a custom instance file and a custom maximum number of reference points
./build/release/examples/example_mokp /path/to/instance.in 3The example compares all three strategies (MIQP, Log, and MILP) for reference-point set sizes (k = 1, \ldots, K). For each (k), it generates 30 (default) random non-dominated reference-point sets (each of size (k)), measures wall-clock time, and validates each result against exhaustive enumeration over the known Pareto front.
The script examples/test_mokp.sh automates running example_mokp across all
bundled benchmark instances and collects pass/fail results:
# Quick sanity check (small instances only, max_k = 3)
./examples/test_mokp.sh --quick
# Full run over all instances (max_k = 5, the default)
./examples/test_mokp.sh --all
# Custom options
./examples/test_mokp.sh --max-k 4 --dims 3 --timeout 120Run ./examples/test_mokp.sh --help for the complete list of options.
The results are saved in examples/test_results/ (inside the build directory) with timestamps, and a summary table is printed to the console.
#include <hs_moco/hs_moco.hpp>
// 1. Define items
std::vector<hs_moco::MOKPItem> items = {
{0, 2, {8, 3, 5}},
{1, 3, {4, 7, 6}},
// ...
};
// 2. Create problem + strategy + solver
auto problem = std::make_unique<hs_moco::MOKProblem>(std::move(items), /*W=*/10);
auto strategy = std::make_unique<hs_moco::MIQPStrategy>();
hs_moco::SolverOptions opts;
opts.time_limit = 60.0;
hs_moco::MOKPSolver solver(std::move(problem), std::move(strategy), opts);
// 3. Solve repeatedly with different reference points
auto result = solver.solve({{0, 0, 0}});
if (result.status == hs_moco::SolveStatus::Optimal) {
std::cout << "HS = " << result.objective_value << "\n";
}Gurobi must be findable at configure time. mooutils is automatically fetched
via FetchContent if it is not already installed.
add_subdirectory(path/to/hs-moco)
target_link_libraries(your_target PRIVATE hs_moco::hs_moco)include(FetchContent)
FetchContent_Declare(
hs_moco
GIT_REPOSITORY https://github.com/gaplopes/hs-moco.git
GIT_TAG main
)
FetchContent_MakeAvailable(hs_moco)
target_link_libraries(your_target PRIVATE hs_moco::hs_moco)cmake --install build/release --prefix /usr/localfind_package(hs_moco REQUIRED)
target_link_libraries(your_target PRIVATE hs_moco::hs_moco)hs-moco/
├── CMakeLists.txt
├── CMakePresets.json
├── cmake/
│ ├── hs_moco-config.cmake.in
│ └── modules/
│ └── FindGUROBI.cmake
├── include/
│ └── hs_moco/
│ ├── hs_moco.hpp ← umbrella header
│ ├── core/
│ │ ├── types.hpp ← SolveStatus, SolverOptions, SolveResult
│ │ ├── strategy.hpp ← HSStrategy, AuxiliaryState, StrategyContext
│ │ └── solver.hpp ← MOKPSolver orchestrator
│ ├── strategies/
│ │ ├── miqp.hpp ← bilinear product (NonConvex=2)
│ │ ├── log.hpp ← log-sum-exp (FuncNonlinear=1)
│ │ └── milp.hpp ← McCormick linearization
│ └── problems/
│ └── mokp.hpp ← Multiobjective Knapsack Problem
├── examples/
│ ├── CMakeLists.txt
│ ├── mokp.cpp ← MOKP strategy-comparison example
│ └── test_mokp.sh ← batch test script across instances
└── README.md
Contributions are welcome! If you have bug fixes, new problem implementations, additional strategies, or other improvements, please fork the repository and open a pull request. For major changes, consider opening an issue first to discuss the approach.
@software{lopes2026hsmoco,
author = {Lopes, Gon{\c{c}}alo},
title = {{HS-MOCO}: Hypervolume Scalarization for the Multiobjective
Knapsack Problem},
year = {2026},
url = {https://github.com/gaplopes/hs-moco}
}This project is licensed under the MIT License - see the LICENSE file for details.
For any questions or issues, please open an issue on the repository or contact the author at galopes@dei.uc.pt or via GitHub or LinkedIn (see profile for contact information).