diff --git a/README.md b/README.md index ec7899d..05cd1ed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - This repository contains various benchmark instances for the Vector Bin Packing and one-dimensional Bin Packing problems. Multi-dimensional instances @@ -9,7 +8,7 @@ The `multi_dimensions` folder contains instances for the Vector Bin Packing prob This folder currently contains the 3 benchmarks (_Panigrahy_, _Triplet_ and _New_) used for the [experimental part](https://github.com/Vectorpack/experiments_vector_paper) of the paper "_Classification and evaluation of the algorithms for vector bin packing_", published in [Computers and Operations Research journal](https://www.sciencedirect.com/science/article/pii/S0305054824003320) and openly available on [HAL](https://inria.hal.science/hal-04769128v1). -**Folder content** +**Folder content:** - Instance files: all instance files used in the paper, archived in `tar gz` format. Instance files follow the VBP format introduced for the [VPSolver](https://github.com/fdabrandao/vpsolver) by F. Brandão (see [manual here](https://github.com/fdabrandao/vpsolver/blob/master/docs/vpsolver_manual.pdf)). @@ -20,3 +19,18 @@ This folder currently contains the 3 benchmarks (_Panigrahy_, _Triplet_ and _New If you need to refer to these multi-dimensional benchmarks, please cite our paper: Clément Mommessin, Thomas Erlebach, and Natalia Shakhlevich. "_Classification and evaluation of the algorithms for vector bin packing_". In Computers and Operations Research, 173, 2025. + + +One-dimensional instances +------------------------- + +The `one_dimension_conflicts` folder contains a BPPC benchmark dataset and an instance generator for the one-dimensional Bin Packing Problem with Conflicts. + +**Folder content:** + +- Benchmark dataset: the `BPPC_dataset.tar.gz` archive contains instances generated from the classical one-dimensional Bin Packing Problem instances proposed by E. Falkenauer in ["_A hybrid grouping genetic algorithm for bin packing_"](https://link.springer.com/article/10.1007/BF00226291), Journal of Heuristics, 2(1):5–30, 1996. + The dataset is organized into eight benchmark classes, two conflict-generation variants, and several conflict graph densities. +- Instance generator: the `BPPC_instance_generator` folder contains a standalone C++ program for generating random arbitrary conflict graphs for one-dimensional BPP instances. + The generator supports input formats consistent with the VBP format and BPPLIB format (see [BPPLIB](https://github.com/mdelorme2/BPPLIB) to find a great collection of BPP instances). + +For further details on the dataset structure, generation process, and instance file format, please refer to the included [README](`one_dimension_conflicts/README.txt`) file. diff --git a/one_dimension_conflicts/BPPC_dataset.tar.gz b/one_dimension_conflicts/BPPC_dataset.tar.gz new file mode 100644 index 0000000..a54bd1b Binary files /dev/null and b/one_dimension_conflicts/BPPC_dataset.tar.gz differ diff --git a/one_dimension_conflicts/BPPC_instance_generator/Makefile b/one_dimension_conflicts/BPPC_instance_generator/Makefile new file mode 100644 index 0000000..609d8bc --- /dev/null +++ b/one_dimension_conflicts/BPPC_instance_generator/Makefile @@ -0,0 +1,23 @@ +CXX = g++ +CXXFLAGS = -Wall -Wextra -std=c++17 + +BUILD_DIR = build + +TARGET = $(BUILD_DIR)/bppc_generator.exe + +SRCS = generator.cpp main.cpp +OBJS = $(SRCS:%.cpp=$(BUILD_DIR)/%.o) + +all: $(BUILD_DIR) $(TARGET) + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) + +$(BUILD_DIR)/%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +clean: + rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/one_dimension_conflicts/BPPC_instance_generator/generator.cpp b/one_dimension_conflicts/BPPC_instance_generator/generator.cpp new file mode 100644 index 0000000..136f98b --- /dev/null +++ b/one_dimension_conflicts/BPPC_instance_generator/generator.cpp @@ -0,0 +1,177 @@ +#include "generator.hpp" + +#include +#include +#include +#include +#include +#include + +// PSEUDOCODE FOR THE p̂-generator +// Source: Gendreau et al. "Solving the maximum clique problem using a tabu search" Annals of Operations Research 41(1993) 385-403 +// 0 <= a <= b <= 1 +// begin +// for i := 1 to n do p[i] := uniform (a,b); +// for i := 1 to (n- 1) do +// for j := (i + 1) to n do +// generate edge (i, j) with probability ( p[i] + p[j] ) / 2; +// end + +ArbitraryGraphGenerator::ArbitraryGraphGenerator(int N, float A, float B, int seed) + :verticesCount(N), + aBound(A), + bBound(B), + adjacencyLists(N), + gen(seed) { // Standard mersenne_twister_engine seeded with the given seed value + + if (N <= 0) + throw std::invalid_argument("N must be > 0"); + else if (A < 0 || B > 1 || A > B) + throw std::invalid_argument("A and B have to be real numbers in [0,1] & A <= B"); + else { + // IMPORTANT: uniform_real_distribution generates a real value only from [min, max). + // The exact `max` value MAY, or MAY NOT be returned. This uncertainty is due to + // undeterministic floating-point rounding; see e.g. cppreference.com + std::uniform_real_distribution<> dist(A, B); + + for(int i = 0; i < N; i++) + verticesProbs.push_back(dist(gen)); + } +} + +// This function generates an edge between two different vertices in arbitrary conflict graph, +// or a conflict between two different BPPC items, in other words. +// The function saves generated edges as adjacencyLists. +// Every vertex has its own adjacency list, which contains all its adjacent vertices in the context of arbitrary conflict graph +void ArbitraryGraphGenerator::generateEdges() { + double prob; + std::bernoulli_distribution edgeDist; + for (int i = 0; i < verticesCount - 1; i++) + for (int j = i + 1; j < verticesCount; j++) { + // Calculate the probability of an edge (conflict between two items) + prob = (static_cast(verticesProbs.at(i)) + verticesProbs.at(j)) / 2.0; + + // Next, Bernoulli Distribution makes a decision whether + // the vertices i and j should be connected with an edge + // with **prob** likelihood, + // or NOT, with (1 - prob) likelihood. + auto param = std::bernoulli_distribution::param_type(prob); + if (edgeDist(gen, param)) { + adjacencyLists.at(i).push_back(j); + //adjacencyLists.at(j).push_back(i); // Uncomment for symmetric adjacency matrix + } + } + // Sort IDs in vectors in ascending order + for(auto& v : adjacencyLists) + std::sort(v.begin(), v.end()); +} + +// Write all conflicts in a row for an item with ID = index +std::string writeConflicts(const ArbitraryGraphGenerator* g, int index, OutputFileType outputFileType) { + std::string conflictsList; + for (int id : g->adjacencyLists.at(index)) + conflictsList += " " + (outputFileType == OutputFileType::Textfile ? std::to_string(id + 1) : std::to_string(id)); // Conversion to 1-based item ID's for text output file's format + + return conflictsList; +} + +// This function creates a single BPPC instance +// Make sure you have your input files format consistent with BPP or VBP instance format. +void ArbitraryGraphGenerator::createBPPCInstance(const std::string& inputFilename, const std::string& outputFilename, InputFileType inputFileType, OutputFileType outputFileType) { + + std::string ext = inputFilename.substr(inputFilename.size() - 4); + if (inputFilename.size() < 4 || (ext != ".txt" && ext != ".vbp")) + throw std::runtime_error("Expected *.txt or *.vbp file"); + + std::ifstream fileIn(inputFilename); + if (!fileIn) + throw std::runtime_error("Cannot open file: " + inputFilename); + + std::ofstream fileOut(outputFilename); + if (!fileOut) + throw std::runtime_error("Cannot create file: " + outputFilename); + + if (inputFileType == InputFileType::BPPLIB) { + std::string itemsCountLine; + std::string capacityLine; + + std::getline(fileIn, itemsCountLine); + std::getline(fileIn, capacityLine); + + int n = std::stoi(itemsCountLine); + int capacity = std::stoi(capacityLine); + + // Create 1-Dimensional BPPC instance text file + fileOut << '1' << std::endl; // Number of dimensions; the first line of a new file + fileOut << capacity << std::endl; // Bins capacity; the second line + fileOut << n << std::endl; // Number of items; the third line + + // Then save a new line to the output file in the required format + std::string line; + if (outputFileType == OutputFileType::Textfile) { + for (int i = 0; i < n; i++) { + fileIn >> line; + fileOut << std::to_string(i+1) + " " + line + writeConflicts(this, i, outputFileType) << std::endl; + } + } + else if (outputFileType == OutputFileType::VBP) { + for (int i = 0; i < n; i++) { + fileIn >> line; + fileOut << line + " 1" + writeConflicts(this, i, outputFileType) << std::endl; + } + } + else { + throw std::runtime_error("Unsupported output file type"); + } + + } + else if (inputFileType == InputFileType::VBP) { + std::string dimensionsLine; + std::string capacitiesLine; + std::string itemsCountLine; + + std::getline(fileIn, dimensionsLine); + std::getline(fileIn, capacitiesLine); + std::getline(fileIn, itemsCountLine); + + int dimensions = std::stoi(dimensionsLine); + if (dimensions <= 0) + throw std::runtime_error("VBP dimensions must be positive"); + + int n = std::stoi(itemsCountLine); + + fileOut << dimensionsLine << std::endl; + fileOut << capacitiesLine << std::endl; + fileOut << itemsCountLine << std::endl; + + if (outputFileType == OutputFileType::Textfile) { + for (int i = 0; i < n; i++) { + std::string itemSizesLine, temp; + std::getline(fileIn, itemSizesLine); + std::stringstream ss(itemSizesLine); + fileOut << std::to_string(i+1); + for (int j = 0; j < std::stoi(dimensionsLine); j++) { + ss >> temp; + fileOut << " " << temp; + } + fileOut << writeConflicts(this, i, outputFileType) << std::endl; + } + } + else if (outputFileType == OutputFileType::VBP) { + for (int i = 0; i < n; i++) { + std::string itemSizesLine; + std::getline(fileIn, itemSizesLine); + fileOut << itemSizesLine << writeConflicts(this, i, outputFileType) << std::endl; + } + } + else { + throw std::runtime_error("Unsupported output file type"); + } + } + else { + throw std::runtime_error("Unsupported input file type"); + } + + fileIn.close(); + fileOut.close(); +} diff --git a/one_dimension_conflicts/BPPC_instance_generator/generator.hpp b/one_dimension_conflicts/BPPC_instance_generator/generator.hpp new file mode 100644 index 0000000..d214471 --- /dev/null +++ b/one_dimension_conflicts/BPPC_instance_generator/generator.hpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include + +enum class InputFileType { + BPPLIB, + VBP +}; + +enum class OutputFileType { + Textfile, + VBP +}; + +class ArbitraryGraphGenerator { +private: + int verticesCount; + float aBound, bBound; + std::vector> adjacencyLists; // Lists of conflicts - consecutive indices correspond to items IDs (starting with 0) + std::mt19937 gen; // Equidistributed Uniform Pseudo-Random Number Generator + std::vector verticesProbs; // Probability for each vertex of being in conflict with other vertices + +public: + ArbitraryGraphGenerator(int N, float A, float B, int seed); + void generateEdges(); + friend std::string writeConflicts(const ArbitraryGraphGenerator* g, int index, OutputFileType outputFileType); + void createBPPCInstance(const std::string& inputFilename, const std::string& outputFilename, InputFileType inputFileType, OutputFileType outputFileType); +}; diff --git a/one_dimension_conflicts/BPPC_instance_generator/main.cpp b/one_dimension_conflicts/BPPC_instance_generator/main.cpp new file mode 100644 index 0000000..d5ecdeb --- /dev/null +++ b/one_dimension_conflicts/BPPC_instance_generator/main.cpp @@ -0,0 +1,250 @@ +// This program implements p̂-generator presented and described by M. Gendreau, P. Soriano & L. Salvail +// in "Solving the maximum clique problem using a tabu search approach", Annals of Operations Research 41(1993) 385-403. +// The program is able to generate any amount of 1D BPPC instances from a given folder, or from a single input text / VBP file. + +#include "generator.hpp" + +#include +#include +#include +#include +#include +#include +#include + +InputFileType parseInputFileType(const std::string& value) { + if (value == "BPPLIB") + return InputFileType::BPPLIB; + + if (value == "VBP") + return InputFileType::VBP; + + throw std::invalid_argument("Invalid input file type. Allowed values: BPPLIB, VBP"); +} + +OutputFileType parseOutputFileType(const std::string& value) { + if (value == "Textfile") + return OutputFileType::Textfile; + + if (value == "VBP") + return OutputFileType::VBP; + + throw std::invalid_argument("Invalid output file type. Allowed values: Textfile, VBP"); +} + +int readItemsCount(const std::string& filename, InputFileType inputFileType) { + std::ifstream file(filename); + if (!file) + throw std::runtime_error("Cannot open file: " + filename); + + if (inputFileType == InputFileType::BPPLIB) { + std::string itemsCountLine; + std::getline(file, itemsCountLine); + + return std::stoi(itemsCountLine); + } + else if (inputFileType == InputFileType::VBP) { + std::string dimensionsLine; + std::string capacitiesLine; + std::string itemsCountLine; + + std::getline(file, dimensionsLine); + std::getline(file, capacitiesLine); + std::getline(file, itemsCountLine); + + return std::stoi(itemsCountLine); + } + throw std::runtime_error("Unsupported input file type"); +} + +void generateForOneFile(const std::string& filename, float A, float B, int seed, InputFileType inputFileType, OutputFileType outputFileType) { + int n = readItemsCount(filename, inputFileType); + + ArbitraryGraphGenerator generator(n, A, B, seed); + + generator.generateEdges(); + std::filesystem::path inputPath(filename); + std::filesystem::path outputFile = inputPath.parent_path() / (inputPath.stem().string() + (outputFileType == OutputFileType::Textfile ? "_BPPC.txt" : "_BPPC.vbp" )); + + generator.createBPPCInstance(filename, outputFile.string(), inputFileType, outputFileType); + std::cout << "Created BPPC instance for: " << filename << " with seed = " << seed << std::endl; +} + +void generateForFolder(const std::string& foldername, float A, float B, int seed, InputFileType inputFileType, OutputFileType outputFileType) { + std::filesystem::path inputDir(foldername); + std::filesystem::path outputDir = inputDir.string() + "_BPPC"; + std::filesystem::create_directories(outputDir); + + std::vector files; + + for (const auto& entry : std::filesystem::directory_iterator(inputDir)) + if (entry.is_regular_file() && (entry.path().extension() == ".txt" || entry.path().extension() == ".vbp")) + files.push_back(entry.path()); + + std::sort(files.begin(), files.end()); + + for (int instanceIdx = 0; instanceIdx < static_cast(files.size()); ++instanceIdx) { + const std::filesystem::path& filePath = files[instanceIdx]; + + int n = readItemsCount(filePath.string(), inputFileType); + int instanceSeed = seed + instanceIdx; + + ArbitraryGraphGenerator generator(n, A, B, instanceSeed); + generator.generateEdges(); + + std::filesystem::path outputFile = outputDir / (filePath.stem().string() + (outputFileType == OutputFileType::Textfile ? ".txt" : ".vbp" )); + generator.createBPPCInstance(filePath.string(), outputFile.string(), inputFileType, outputFileType); + + std::cout << "Created BPPC instance for: " << outputFile.string() << " with seed = " << instanceSeed << std::endl; + } +} + +void printUsage(const std::string& programName) { + std::cerr + << "BPPC Instance Generator\n" + << "----------------\n" + << "Generates BPPC instances by adding an arbitrary conflict graph to BPP instances.\n\n" + + << "Usage:\n\n" + << " Single input file:\n" + << " " << programName + << " --input-file-type --output-file-type \n\n" + + << " All .txt or .vbp files in a folder:\n" + << " " << programName + << " --input-file-type --output-file-type --generate-for-folder \n\n" + + << "Input file types:\n" + << " BPPLIB\n" + << " Input format:\n" + << " \n" + << " \n" + << " \n" + << " \n" + << " ...\n" + << " \n" + + << " VBP\n" + << " Input format:\n" + << " \n" + << " ... \n" + << " \n" + << " ... \n" + << " ... \n" + << " ...\n" + << " ... \n" + + << "Output file types:\n" + << " Textfile\n" + << " Output format:\n" + << " 1\n" + << " \n" + << " \n" + << " 1 \n" + << " ... \n" + << " \n" + << " ... ...\n" + << " ... ...\n" + << " ...\n" + << " ... ...\n" + + << "Arguments:\n" + << " Lower bound of p-hat values, in [0,1]\n" + << " Upper bound of p-hat values, in [0,1], B >= A\n" + << " Base seed for reproducible conflict graph generation.\n" + << " In folder mode, file k uses seed + k after sorting filenames.\n\n" + + << "Constraints:\n" + << " 0.0 <= A <= B <= 1.0\n\n" + + << "Examples:\n" + << " " << programName + << " --input-file-type BPPLIB --output-file-type Textfile instances/u500_00.txt 0.1 0.3 42\n\n" + << " " << programName + << " --input-file-type VBP --output-file-type VBP instances/vectorpack_instances 0.1 0.3 42\n" + << " This example is incomplete for a folder. Use --generate-for-folder:\n\n" + << " " << programName + << " --input-file-type BPPLIB --output-file-type VBP --generate-for-folder instances/variant 0.1 0.3 42\n"; +} + +int main(int argc, char** argv) { + try { + if ( argc == 2 && (std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") ) { + printUsage(argv[0]); + return 0; + } + + if (argc < 4 || std::string(argv[1]) != "--input-file-type" || std::string(argv[3]) != "--output-file-type") { + printUsage(argv[0]); + return 1; + } + + InputFileType inputFileType = parseInputFileType(argv[2]); + OutputFileType outputFileType = parseOutputFileType(argv[4]); + + bool generateForFolderMode = false; + std::string path; + float A, B; + int seedVal; + + try { + if (argc == 10) { + if (std::string(argv[5]) != "--generate-for-folder") + throw std::invalid_argument("Unknown option: " + std::string(argv[3])); + + generateForFolderMode = true; + + path = argv[6]; + A = std::stof(argv[7]); + B = std::stof(argv[8]); + seedVal = std::stoi(argv[9]); + } + else { + path = argv[5]; + A = std::stof(argv[6]); + B = std::stof(argv[7]); + seedVal = std::stoi(argv[8]); + } + } + catch (const std::exception&) { + throw std::invalid_argument("A, B and seed must be valid numeric values"); + } + + if (A < 0.0f || B > 1.0f || A > B) + throw std::invalid_argument("A and B have to be real numbers in [0,1] and A <= B"); + + if (generateForFolderMode) { + if (!std::filesystem::exists(path)) + throw std::runtime_error("Folder does not exist: " + path); + + if (!std::filesystem::is_directory(path)) + throw std::runtime_error("Path is not a directory: " + path); + + generateForFolder(path, A, B, seedVal, inputFileType, outputFileType); + } + else { + if (!std::filesystem::exists(path)) + throw std::runtime_error("File does not exist: " + path); + + if (!std::filesystem::is_regular_file(path)) + throw std::runtime_error("Path is not a regular file: " + path); + + generateForOneFile(path, A, B, seedVal, inputFileType, outputFileType); + } + } + catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + std::cerr << std::endl; + printUsage(argv[0]); + return 1; + } + + return 0; +}