From ae84696397db9cfb17cfbc6333ba407054dcc29a Mon Sep 17 00:00:00 2001 From: legraina Date: Fri, 6 Mar 2026 12:53:00 +0100 Subject: [PATCH 01/50] Move the list of non-dominated labels to a Bucket class. --- src/rcspp/algorithm/buckets.hpp | 78 +++++++++++++++++++ src/rcspp/algorithm/dominance_algorithm.hpp | 45 ++++------- .../algorithm/pulling_dominance_algorithm.hpp | 1 - src/rcspp/rcspp.hpp | 1 + 4 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 src/rcspp/algorithm/buckets.hpp diff --git a/src/rcspp/algorithm/buckets.hpp b/src/rcspp/algorithm/buckets.hpp new file mode 100644 index 00000000..b3d1a474 --- /dev/null +++ b/src/rcspp/algorithm/buckets.hpp @@ -0,0 +1,78 @@ +// Copyright (c) 2025 Laboratory for Combinatorial Optimization in Real-time Environment. +// All rights reserved. + +#pragma once + +#include +#include +#include +#include + +#include "rcspp/label/label.hpp" + +namespace rcspp { + +template +class Buckets { + using LabelPosition = std::list*>::iterator; + + template + class Bucket { + LabelPosition begin, end; + RType min_value, max_value; + }; + + public: + explicit Buckets() = default; + + [[nodiscard]] const std::list*>& get_labels() const { return labels_; } + + LabelPosition add_label(Label* label) { + return labels_.insert(labels_.end(), label); + } + + void erase_label(const LabelPosition& pos) { labels_.erase(pos); } + + size_t remove_dominated_labels(const Label& label) { + // Remove all dominated label + size_t removed = 0; + for (auto non_dominated_label_it = labels_.begin(); + non_dominated_label_it != labels_.end();) { + if (&label != *non_dominated_label_it && label <= *(*non_dominated_label_it)) { + (*non_dominated_label_it)->dominated = true; + non_dominated_label_it = labels_.erase(non_dominated_label_it); + ++removed; + } else { + ++non_dominated_label_it; + } + } + return removed; + } + + bool is_dominated(const Label& label) const { + for (const auto non_dominated_label_ptr : labels_) { + if (&label == non_dominated_label_ptr) { + continue; + } + if ((*non_dominated_label_ptr) <= label) { + return true; + } + } + return false; + } + + [[nodiscard]] size_t get_num_buckets() const { return buckets_.size(); } + + private: + std::list*> labels_; + std::vector> buckets_; + + size_t get_bucket_index(Label* label) const { + // Implement a method to determine the appropriate bucket index for the given label + // This could be based on the label's cost, resource usage, or any other relevant + // criteria For example, you could use a simple hash function or a more complex + // heuristic + return 0; // Placeholder: replace with actual logic to determine bucket index + } +}; +} // namespace rcspp diff --git a/src/rcspp/algorithm/dominance_algorithm.hpp b/src/rcspp/algorithm/dominance_algorithm.hpp index 9f69e523..237397f5 100644 --- a/src/rcspp/algorithm/dominance_algorithm.hpp +++ b/src/rcspp/algorithm/dominance_algorithm.hpp @@ -9,6 +9,7 @@ #include #include "rcspp/algorithm/algorithm.hpp" +#include "rcspp/algorithm/buckets.hpp" #include "rcspp/label/label_pool.hpp" namespace rcspp { @@ -29,9 +30,9 @@ class DominanceAlgorithm : public Algorithm { auto* source_node = this->graph_->get_node(source_node_id); auto& label = this->label_pool_.get_next_label(source_node); - auto& labels = non_dominated_labels_by_node_pos_.at(source_node->pos()); + auto& buckets = non_dominated_labels_by_node_pos_.at(source_node->pos()); // it points to the newly inserted element - auto label_it = labels.insert(labels.end(), &label); + auto label_it = buckets.add_label(&label); add_new_unprocessed_label(std::make_pair(&label, label_it)); } } @@ -106,11 +107,10 @@ class DominanceAlgorithm : public Algorithm { if (feasible && update_non_dominated_labels(new_label)) { // Add to unprocessed_labels_ and non_dominated_labels_by_node_id_ only if // feasible and non dominated. - auto& non_dominated_labels = - non_dominated_labels_by_node_pos_.at(new_label.get_end_node()->pos()); // points to the newly inserted element auto new_label_it = - non_dominated_labels.insert(non_dominated_labels.end(), &new_label); + non_dominated_labels_by_node_pos_.at(new_label.get_end_node()->pos()) + .add_label(&new_label); add_new_unprocessed_label(std::make_pair(&new_label, new_label_it)); } else { if (!feasible) { @@ -137,7 +137,7 @@ class DominanceAlgorithm : public Algorithm { while (prev_node_ptr != nullptr) { bool found = false; for (const auto label_ptr : - non_dominated_labels_by_node_pos_.at(prev_node_ptr->pos())) { + non_dominated_labels_by_node_pos_.at(prev_node_ptr->pos()).get_labels()) { // if cannot reach the current label from this label, skip it if (!label_ptr->is_reachable(in_arc_ptr->destination->id)) { continue; @@ -190,32 +190,14 @@ class DominanceAlgorithm : public Algorithm { non_dominated_labels_by_node_pos_.at(current_node_pos); // First, check if label is dominated by any existing non-dominated label - bool label_dominated = false; - for (const auto non_dominated_label_ptr : non_dominated_labels_list) { - if (&label == non_dominated_label_ptr) { - continue; - } - if ((*non_dominated_label_ptr) <= label) { - label_dominated = true; - break; - } - } + bool label_dominated = non_dominated_labels_list.is_dominated(label); if (label_dominated) { total_update_non_dom_time_.stop(); return false; } - // Second, remove all existing non-dominated labels that are dominated by label - for (auto non_dominated_label_it = non_dominated_labels_list.begin(); - non_dominated_label_it != non_dominated_labels_list.end();) { - if (&label != *non_dominated_label_it && label <= *(*non_dominated_label_it)) { - (*non_dominated_label_it)->dominated = true; - non_dominated_label_it = - non_dominated_labels_list.erase(non_dominated_label_it); - } else { - ++non_dominated_label_it; - } - } + // Second, remove all existing labels that are dominated by label + non_dominated_labels_list.remove_dominated_labels(label); total_update_non_dom_time_.stop(); @@ -224,14 +206,15 @@ class DominanceAlgorithm : public Algorithm { virtual void remove_label(const std::list*>::iterator& label_iterator) { auto current_node_pos = (*label_iterator)->get_end_node()->pos(); - non_dominated_labels_by_node_pos_.at(current_node_pos).erase(label_iterator); + non_dominated_labels_by_node_pos_.at(current_node_pos).erase_label(label_iterator); } [[nodiscard]] std::list*> get_labels_at_sinks() const override { std::list*> labels_at_sinks; for (auto sink_node_id : this->graph_->get_sink_node_ids()) { auto node_pos = this->graph_->get_node(sink_node_id)->pos(); - const auto& labels_at_current_sink = non_dominated_labels_by_node_pos_.at(node_pos); + const auto& labels_at_current_sink = + non_dominated_labels_by_node_pos_.at(node_pos).get_labels(); labels_at_sinks.insert(labels_at_sinks.end(), labels_at_current_sink.begin(), labels_at_current_sink.end()); @@ -244,7 +227,7 @@ class DominanceAlgorithm : public Algorithm { LOG_DEBUG("All non dominated labels by node:\n"); for (size_t pos = 0; pos < non_dominated_labels_by_node_pos_.size(); pos++) { LOG_DEBUG("Node ", this->graph_->get_sorted_nodes().at(pos)->id, ":\n"); - for (auto label_ptr : non_dominated_labels_by_node_pos_.at(pos)) { + for (auto label_ptr : non_dominated_labels_by_node_pos_.at(pos).get_labels()) { LOG_DEBUG(" ", label_ptr->get_resource().to_string(), "\n"); } } @@ -253,7 +236,7 @@ class DominanceAlgorithm : public Algorithm { virtual void add_new_unprocessed_label( const LabelIteratorPair& label_iterator_pair) = 0; - std::vector*>> non_dominated_labels_by_node_pos_; + std::vector> non_dominated_labels_by_node_pos_; Timer total_extend_time_; Timer total_update_non_dom_time_; diff --git a/src/rcspp/algorithm/pulling_dominance_algorithm.hpp b/src/rcspp/algorithm/pulling_dominance_algorithm.hpp index e869966c..7e3637d1 100644 --- a/src/rcspp/algorithm/pulling_dominance_algorithm.hpp +++ b/src/rcspp/algorithm/pulling_dominance_algorithm.hpp @@ -57,7 +57,6 @@ class PullingDominanceAlgorithm : public DominanceAlgorithm, this->label_pool_.release_label(&label); it = erase_unprocessed_label(it); // erase label } else { - assert(this->update_non_dominated_labels(label)); // check if sink and update best solution if (label.get_end_node()->sink) { LOG_DEBUG("Found a solution with cost ", label.get_cost(), "\n"); diff --git a/src/rcspp/rcspp.hpp b/src/rcspp/rcspp.hpp index b3488f41..36d3751a 100644 --- a/src/rcspp/rcspp.hpp +++ b/src/rcspp/rcspp.hpp @@ -2,6 +2,7 @@ #pragma once #include "rcspp/algorithm/algorithm.hpp" +#include "rcspp/algorithm/buckets.hpp" #include "rcspp/algorithm/diversification_search.hpp" #include "rcspp/algorithm/dominance_algorithm.hpp" #include "rcspp/algorithm/greedy.hpp" From 1e06699bd001b9bfbd9fe53a7e6449bd4949c5ca Mon Sep 17 00:00:00 2001 From: legraina Date: Mon, 9 Mar 2026 23:12:25 +0100 Subject: [PATCH 02/50] Code running correctly, but still some bugs on the dominance and the buckets. --- src/python_interface/rcspp/graph.cpp | 2 +- src/rcspp/algorithm/algorithm.hpp | 15 +- src/rcspp/algorithm/buckets.hpp | 299 ++++++++++++++++-- .../algorithm/diversification_search.hpp | 17 +- src/rcspp/algorithm/dominance_algorithm.hpp | 21 +- src/rcspp/algorithm/greedy.hpp | 9 +- .../algorithm/pulling_dominance_algorithm.hpp | 10 +- .../algorithm/pushing_dominance_algorithm.hpp | 10 +- .../algorithm/simple_dominance_algorithm.hpp | 10 +- src/rcspp/resource/base/resource.hpp | 5 + .../component_dominance_function.hpp | 9 + .../composition_dominance_function.hpp | 9 + .../resource/concrete/container_resource.hpp | 16 +- .../dominance/contain_dominance_function.hpp | 27 +- .../inclusion_dominance_function.hpp | 6 + .../dominance/value_dominance_function.hpp | 6 + .../dominance/dominance_function.hpp | 5 + .../dominance/trivial_dominance_function.hpp | 7 + src/rcspp/resource/resource_graph.hpp | 89 +++--- src/vrp/benchmark_main.cpp | 6 +- src/vrp/vrp.hpp | 9 +- 21 files changed, 464 insertions(+), 123 deletions(-) diff --git a/src/python_interface/rcspp/graph.cpp b/src/python_interface/rcspp/graph.cpp index 864aa1a7..60c6b2e9 100644 --- a/src/python_interface/rcspp/graph.cpp +++ b/src/python_interface/rcspp/graph.cpp @@ -165,7 +165,7 @@ void init_graph(py::module_& m) { .def("solve", &ResourceGraph::solve, py::arg("upper_bound") = std::numeric_limits::infinity(), - py::arg("params") = AlgorithmParams{}, + py::arg("params") = AlgorithmParams{}, py::arg("preprocess") = true, py::arg("cost_index") = 0) .def("process_feasibility", &ResourceGraph::process_feasibility); diff --git a/src/rcspp/algorithm/algorithm.hpp b/src/rcspp/algorithm/algorithm.hpp index 97c23387..ede9158b 100644 --- a/src/rcspp/algorithm/algorithm.hpp +++ b/src/rcspp/algorithm/algorithm.hpp @@ -17,9 +17,11 @@ #include #include +#include "rcspp/algorithm/buckets.hpp" #include "rcspp/algorithm/solution.hpp" #include "rcspp/graph/graph.hpp" #include "rcspp/label/label_pool.hpp" +#include "rcspp/resource/concrete/numerical_resource.hpp" #include "rcspp/utils/timer.hpp" namespace rcspp { @@ -33,7 +35,10 @@ using LabelIteratorPair = constexpr int MAX_INT = std::numeric_limits::max() / 2; // to avoid overflow +template struct AlgorithmParams { + explicit AlgorithmParams(LabelsType labels = LabelsType()) : labels(std::move(labels)) {} + AlgorithmParams& check() { if (num_max_phases > 1 && num_labels_to_extend_by_node >= MAX_INT) { LOG_WARN( @@ -69,6 +74,9 @@ struct AlgorithmParams { // for using label pool (should normally always be true) bool use_pool = true; + // Container to store labels, could be overridden with Buckets + const LabelsType labels; + // for truncated labeling size_t num_labels_to_extend_by_node = MAX_INT; @@ -87,11 +95,12 @@ struct AlgorithmParams { int seed = 0; }; -template +template > requires std::derived_from> class Algorithm { public: - Algorithm(ResourceFactory* resource_factory, AlgorithmParams params) + Algorithm(ResourceFactory* resource_factory, + AlgorithmParams params) : label_pool_(std::make_unique>(resource_factory)), graph_(nullptr), params_(std::move(params.check())) {} @@ -241,7 +250,7 @@ class Algorithm { LabelPool label_pool_; const Graph* graph_; - const AlgorithmParams params_; + const AlgorithmParams params_; double cost_upper_bound_ = std::numeric_limits::infinity(); std::unordered_set solutions_; diff --git a/src/rcspp/algorithm/buckets.hpp b/src/rcspp/algorithm/buckets.hpp index b3d1a474..34e180fd 100644 --- a/src/rcspp/algorithm/buckets.hpp +++ b/src/rcspp/algorithm/buckets.hpp @@ -13,17 +13,13 @@ namespace rcspp { template -class Buckets { +class Labels { using LabelPosition = std::list*>::iterator; - template - class Bucket { - LabelPosition begin, end; - RType min_value, max_value; - }; - public: - explicit Buckets() = default; + explicit Labels() = default; + + Labels copy() const { return Labels(); } [[nodiscard]] const std::list*>& get_labels() const { return labels_; } @@ -33,6 +29,12 @@ class Buckets { void erase_label(const LabelPosition& pos) { labels_.erase(pos); } + void print_labels() const { + for (auto label_ptr : labels_) { + LOG_DEBUG(" ", label_ptr, ": ", label_ptr->get_resource().to_string(), "\n"); + } + } + size_t remove_dominated_labels(const Label& label) { // Remove all dominated label size_t removed = 0; @@ -61,18 +63,279 @@ class Buckets { return false; } - [[nodiscard]] size_t get_num_buckets() const { return buckets_.size(); } + protected: + std::list*> labels_; +}; + +template +class Buckets : public Labels { + using LabelPosition = std::list*>::iterator; + + template + struct Bucket { + Bucket(const LabelPosition& pos, const RType* value, double range) + : begin(pos), end(std::next(pos)), begin_value(value), range(range) {} + + LabelPosition begin, end; + const RType* begin_value; + double range; + + bool is_within_bucket(const RType& value) const { + // neither before, nor after the bucket + return !is_before_bucket(value) && !is_after_bucket(value); + } + + bool is_before_bucket(const RType& value) const { + return !begin_value->is_lower(value); + } + + bool is_after_bucket(const RType& value) const { + return begin_value->is_lower(value, -range); + } + + void update_begin(const LabelPosition& new_begin, const RType* new_min_value) { + begin = new_begin; + begin_value = new_min_value; + } + + void update_end(const LabelPosition& new_end) { end = new_end; } + }; + + using BucketPosition = std::list>>::iterator; + + public: + Buckets(size_t range_buckets, size_t bucket_resource_index, size_t sort_resource_index) + : range_buckets_(range_buckets), + bucket_resource_index_(bucket_resource_index), + sort_resource_index_(sort_resource_index) {} + + Buckets copy() const { + return Buckets(range_buckets_, bucket_resource_index_, sort_resource_index_); + } + + LabelPosition add_label(Label* label) { + const auto& label_bucket_resource = get_bucket_resource(*label); + auto bit = buckets_.begin(); + while (bit != buckets_.end()) { + auto& bucket = *bit; + if (bucket.is_before_bucket(label_bucket_resource)) { + // Insert a new bucket before the current one and insert the label there + LabelPosition pos = this->labels_.insert(bucket.begin, label); + insert_bucket(bit, pos, &label_bucket_resource); + return pos; + } + if (bucket.is_within_bucket(label_bucket_resource)) { + // Insert the label in the correct position within the bucket based on the sort + // resource + const auto& label_sort_resource = get_sort_resource(*label); + auto it = bucket.begin; + while (it != bucket.end && get_sort_resource(**it) <= label_sort_resource) { + ++it; + } + // insert label at the right position in the list of labels + LabelPosition pos = this->labels_.insert(it, label); + + // update begin if necessary + if (it == bucket.begin) { + update_bucket_begin(bit, pos, &label_bucket_resource); + } + + return pos; + } + ++bit; + } + + // Insert a new bucket at the end of the list and insert the label there + LabelPosition pos = this->labels_.insert(this->labels_.end(), label); + insert_bucket(buckets_.end(), pos, &label_bucket_resource); + return pos; + } + + void erase_label(const LabelPosition& pos) { + // find the bucket containing the erased label + const auto& label_bucket_resource = get_bucket_resource(**pos); + auto bit = std::find_if(buckets_.begin(), buckets_.end(), [&](const auto& bucket) { + return bucket.is_within_bucket(label_bucket_resource); + }); + if (bit == buckets_.end()) { + throw std::runtime_error("Label not found in any bucket"); + } + + // erase the label from the list of labels + auto it = this->labels_.erase(pos); + + // update the bucket begin if first element + // remove the bucket if containing only the label + auto& bucket = *bit; + if (pos == bucket.begin) { + // if contains only the label, remove the bucket + if (it == bucket.end) { + remove_bucket(bit); + } else { + update_bucket_begin(bit, it); + } + } + } + + size_t remove_dominated_labels(const Label& label) { + // if no bucket, no label, return 0 + if (buckets_.empty()) { + return 0; + } + + // Remove all dominated label (starting by upper buckets). Lower buckets cannot be + // dominated + size_t removed = 0; + const auto& label_bucket_resource = get_bucket_resource(label); + const auto& label_sort_resource = get_sort_resource(label); + auto bit = buckets_.end(); + while (bit != buckets_.begin()) { + --bit; + auto& bucket = *bit; + // if the label is after the bucket, we can stop, as all the remaining lower buckets + // are before the label and cannot be dominated + if (bucket.is_after_bucket(label_bucket_resource)) { + break; + } + // check if the labels can be dominated in the bucket + auto non_dominated_label_it = bucket.end; + auto new_begin = bucket.end; + bool reached_begin = false; + while (!reached_begin) { + --non_dominated_label_it; + reached_begin = (non_dominated_label_it == bucket.begin); + if (&label != *non_dominated_label_it && label <= **non_dominated_label_it) { + // remove the dominated label + (*non_dominated_label_it)->dominated = true; + non_dominated_label_it = this->labels_.erase(non_dominated_label_it); + ++removed; + } else { + if (!(get_sort_resource(**non_dominated_label_it) <= label_sort_resource)) { + new_begin = bucket.begin; // not modified as breaking before reaching + // begin or erasing it + // if the sort resource of the current label does not dominate the label + // sort resource, we can stop + break; + } + new_begin = non_dominated_label_it; // update begin flag of the bucket + } + } + + // update or erase the bucket if necessary + // remove empty bucket if either no labels anymore or the new end is at the end of + // the previous bucket + if (new_begin != bucket.begin) { + if (new_begin == bucket.end) { + // all labels in the bucket are dominated, we can remove the bucket + bit = remove_bucket(bit); + } else { + update_bucket_begin(bit, new_begin); + } + } + } + + return removed; + } + + bool is_dominated(const Label& label) const { + // if no bucket, no label, return false + if (buckets_.empty()) { + return false; + } + + // Check all labels (starting by the lower buckets). Upper buckets cannot dominate + const auto& label_bucket_resource = get_bucket_resource(label); + const auto& label_sort_resource = get_sort_resource(label); + for (const auto& bucket : buckets_) { + // if the label is before the bucket, we can stop, as all the following buckets are + // after the label and cannot dominate + if (bucket.is_before_bucket(label_bucket_resource)) { + break; + } + // check if the labels can dominate in the bucket + for (auto it = bucket.begin; it != bucket.end; ++it) { + if (&label == *it) { + continue; + } + if (**it <= label) { + return true; + } + if (!(get_sort_resource(**it) <= label_sort_resource)) { + // if the sort resource of the current label does not dominate the label + // sort resource, we can stop, as the following labels in the bucket are + // sorted by the sort resource and cannot dominate + break; + } + } + } + + return false; + } private: - std::list*> labels_; - std::vector> buckets_; - - size_t get_bucket_index(Label* label) const { - // Implement a method to determine the appropriate bucket index for the given label - // This could be based on the label's cost, resource usage, or any other relevant - // criteria For example, you could use a simple hash function or a more complex - // heuristic - return 0; // Placeholder: replace with actual logic to determine bucket index + size_t range_buckets_; + size_t bucket_resource_index_; + size_t sort_resource_index_; + std::list>> buckets_; + + const Resource& get_bucket_resource( + const Label& label) const { + return get_resource(label, bucket_resource_index_); + } + + const Resource& get_sort_resource(const Label& label) const { + return get_resource(label, sort_resource_index_); + } + + void insert_bucket(const BucketPosition& bit, const LabelPosition& begin, + const Resource* begin_bucket_resource = nullptr) { + // update previous end to the begin of the new bucket, as the current bucket will be + // added + if (bit != buckets_.begin()) { + std::prev(bit)->update_end(begin); + } + // insert the new bucket and bucket resource if not provided + if (begin_bucket_resource == nullptr) { + begin_bucket_resource = &get_bucket_resource(**begin); + } + Bucket> new_bucket(begin, + begin_bucket_resource, + range_buckets_); + buckets_.insert(bit, new_bucket); + } + + BucketPosition remove_bucket(const BucketPosition& bit) { + // update previous end to the end of the current bucket, as the current bucket will be + // removed + if (bit != buckets_.begin()) { + // the new end is the begin of the next bucket if exists, otherwise the end of the + // list of labels + auto new_end = + std::next(bit) != buckets_.end() ? std::next(bit)->begin : this->labels_.end(); + std::prev(bit)->update_end(bit->end); + } + // erase the bucket and return the next bucket position + return buckets_.erase(bit); + } + + void update_bucket_begin(const BucketPosition& bit, const LabelPosition& new_begin, + const Resource* begin_bucket_resource = nullptr) { + // update previous end to the new begin of the current bucket, as the current bucket + // will be updated + if (bit != buckets_.begin()) { + std::prev(bit)->update_end(new_begin); + } + // update the current bucket begin and bucket resource if not provided + if (begin_bucket_resource == nullptr) { + begin_bucket_resource = &get_bucket_resource(**new_begin); + } + bit->update_begin(new_begin, begin_bucket_resource); + } + + template + static const Resource& get_resource(const Label& label, + size_t resource_index) { + return label.get_resource().template get_resource_component(resource_index); } }; } // namespace rcspp diff --git a/src/rcspp/algorithm/diversification_search.hpp b/src/rcspp/algorithm/diversification_search.hpp index bb33f5af..b6612e9d 100644 --- a/src/rcspp/algorithm/diversification_search.hpp +++ b/src/rcspp/algorithm/diversification_search.hpp @@ -36,13 +36,13 @@ namespace rcspp { * Usage: Construct with a resource factory, algorithm parameters, and a unique_ptr to the wrapped * algorithm. */ -template -class DiversificationSearch : public Algorithm { +template > +class DiversificationSearch : public Algorithm { public: DiversificationSearch(ResourceFactory* resource_factory, - AlgorithmParams params, - std::unique_ptr> algo = nullptr) - : Algorithm(resource_factory, std::move(params)), + AlgorithmParams params, + std::unique_ptr> algo = nullptr) + : Algorithm(resource_factory, std::move(params)), algo_(std::move(algo)), rnd_(std::random_device{}()) { // NOLINT(whitespace/braces) rnd_.seed(this->params_.seed); @@ -53,7 +53,8 @@ class DiversificationSearch : public Algorithm { alg_params.stop_after_X_solutions = 1; // only need one solution per iteration alg_params.max_iterations = 20; // ensure early termination if needed // NOLINT algo_ = - std::make_unique>(resource_factory, alg_params); + std::make_unique>(resource_factory, + alg_params); } } @@ -63,7 +64,7 @@ class DiversificationSearch : public Algorithm { // runs up to max_iterations or stop_after_X_solutions. protected: void initialize(const Graph* graph, double cost_upper_bound) override { - Algorithm::initialize(graph, cost_upper_bound); + Algorithm::initialize(graph, cost_upper_bound); graph_copy_ = std::move(graph->clone()); } void main_loop() override { @@ -159,7 +160,7 @@ class DiversificationSearch : public Algorithm { private: std::unique_ptr> graph_copy_; - std::unique_ptr> algo_; + std::unique_ptr> algo_; std::map removed_tabu_arc_ids_; size_t tabu_tenure_extra_{0}; std::mt19937_64 rnd_; diff --git a/src/rcspp/algorithm/dominance_algorithm.hpp b/src/rcspp/algorithm/dominance_algorithm.hpp index 237397f5..cea8e199 100644 --- a/src/rcspp/algorithm/dominance_algorithm.hpp +++ b/src/rcspp/algorithm/dominance_algorithm.hpp @@ -14,17 +14,21 @@ namespace rcspp { -template +template > requires std::derived_from> -class DominanceAlgorithm : public Algorithm { +class DominanceAlgorithm : public Algorithm { public: - DominanceAlgorithm(ResourceFactory* resource_factory, AlgorithmParams params) - : Algorithm(resource_factory, std::move(params)) {} + DominanceAlgorithm(ResourceFactory* resource_factory, + AlgorithmParams params) + : Algorithm(resource_factory, std::move(params)) {} protected: void initialize_labels() override { non_dominated_labels_by_node_pos_.clear(); - non_dominated_labels_by_node_pos_.resize(this->graph_->get_number_of_nodes()); + non_dominated_labels_by_node_pos_.reserve(this->graph_->get_number_of_nodes()); + for (size_t i = 0; i < this->graph_->get_number_of_nodes(); i++) { + non_dominated_labels_by_node_pos_.emplace_back(this->params_.labels.copy()); + } for (auto source_node_id : this->graph_->get_source_node_ids()) { auto* source_node = this->graph_->get_node(source_node_id); @@ -227,16 +231,15 @@ class DominanceAlgorithm : public Algorithm { LOG_DEBUG("All non dominated labels by node:\n"); for (size_t pos = 0; pos < non_dominated_labels_by_node_pos_.size(); pos++) { LOG_DEBUG("Node ", this->graph_->get_sorted_nodes().at(pos)->id, ":\n"); - for (auto label_ptr : non_dominated_labels_by_node_pos_.at(pos).get_labels()) { - LOG_DEBUG(" ", label_ptr->get_resource().to_string(), "\n"); - } + non_dominated_labels_by_node_pos_.at(pos).print_labels(); } } virtual void add_new_unprocessed_label( const LabelIteratorPair& label_iterator_pair) = 0; - std::vector> non_dominated_labels_by_node_pos_; + // Use the template LabelsType + std::vector non_dominated_labels_by_node_pos_; Timer total_extend_time_; Timer total_update_non_dom_time_; diff --git a/src/rcspp/algorithm/greedy.hpp b/src/rcspp/algorithm/greedy.hpp index b6f5c7b6..1acfdcfb 100644 --- a/src/rcspp/algorithm/greedy.hpp +++ b/src/rcspp/algorithm/greedy.hpp @@ -27,11 +27,12 @@ namespace rcspp { * graphs where full enumeration is computationally expensive, and a balance between speed and * solution quality is desired. */ -template -class GreedyAlgorithm : public Algorithm { +template > +class GreedyAlgorithm : public Algorithm { public: - GreedyAlgorithm(ResourceFactory* resource_factory, AlgorithmParams params) - : Algorithm(resource_factory, std::move(params)) {} + GreedyAlgorithm(ResourceFactory* resource_factory, + AlgorithmParams params) + : Algorithm(resource_factory, std::move(params)) {} protected: void main_loop() override { diff --git a/src/rcspp/algorithm/pulling_dominance_algorithm.hpp b/src/rcspp/algorithm/pulling_dominance_algorithm.hpp index 7e3637d1..68eb4b82 100644 --- a/src/rcspp/algorithm/pulling_dominance_algorithm.hpp +++ b/src/rcspp/algorithm/pulling_dominance_algorithm.hpp @@ -11,21 +11,21 @@ #include "rcspp/algorithm/dominance_algorithm.hpp" namespace rcspp { -template +template > requires std::derived_from> -class PullingDominanceAlgorithm : public DominanceAlgorithm, +class PullingDominanceAlgorithm : public DominanceAlgorithm, NodeUnprocessedLabelsManager { public: PullingDominanceAlgorithm(ResourceFactory* resource_factory, - AlgorithmParams params) - : DominanceAlgorithm(resource_factory, std::move(params)), + AlgorithmParams params) + : DominanceAlgorithm(resource_factory, std::move(params)), NodeUnprocessedLabelsManager() {} ~PullingDominanceAlgorithm() override = default; protected: void initialize(const Graph* graph, double cost_upper_bound) override { - Algorithm::initialize(graph, cost_upper_bound); + Algorithm::initialize(graph, cost_upper_bound); this->initialize_unprocessed_labels(graph->get_number_of_nodes()); } diff --git a/src/rcspp/algorithm/pushing_dominance_algorithm.hpp b/src/rcspp/algorithm/pushing_dominance_algorithm.hpp index 58e56cae..8d013344 100644 --- a/src/rcspp/algorithm/pushing_dominance_algorithm.hpp +++ b/src/rcspp/algorithm/pushing_dominance_algorithm.hpp @@ -9,21 +9,21 @@ namespace rcspp { -template +template > requires std::derived_from> -class PushingDominanceAlgorithm : public DominanceAlgorithm, +class PushingDominanceAlgorithm : public DominanceAlgorithm, NodeUnprocessedLabelsManager { public: PushingDominanceAlgorithm(ResourceFactory* resource_factory, - AlgorithmParams params) - : DominanceAlgorithm(resource_factory, std::move(params)), + AlgorithmParams params) + : DominanceAlgorithm(resource_factory, std::move(params)), NodeUnprocessedLabelsManager() {} ~PushingDominanceAlgorithm() override = default; protected: void initialize(const Graph* graph, double cost_upper_bound) override { - Algorithm::initialize(graph, cost_upper_bound); + Algorithm::initialize(graph, cost_upper_bound); this->initialize_unprocessed_labels(graph->get_number_of_nodes()); } diff --git a/src/rcspp/algorithm/simple_dominance_algorithm.hpp b/src/rcspp/algorithm/simple_dominance_algorithm.hpp index 88ff01cc..eabc5a34 100644 --- a/src/rcspp/algorithm/simple_dominance_algorithm.hpp +++ b/src/rcspp/algorithm/simple_dominance_algorithm.hpp @@ -10,19 +10,19 @@ #include "rcspp/algorithm/dominance_algorithm.hpp" namespace rcspp { -template +template > requires std::derived_from> -class SimpleDominanceAlgorithm : public DominanceAlgorithm { +class SimpleDominanceAlgorithm : public DominanceAlgorithm { public: SimpleDominanceAlgorithm(ResourceFactory* resource_factory, - AlgorithmParams params) - : DominanceAlgorithm(resource_factory, std::move(params)) {} + AlgorithmParams params) + : DominanceAlgorithm(resource_factory, std::move(params)) {} ~SimpleDominanceAlgorithm() override = default; private: void initialize(const Graph* graph, double cost_upper_bound) override { - Algorithm::initialize(graph, cost_upper_bound); + Algorithm::initialize(graph, cost_upper_bound); number_of_extended_labels_per_node_.resize(graph->get_number_of_nodes()); } LabelIteratorPair next_label_iterator() override { diff --git a/src/rcspp/resource/base/resource.hpp b/src/rcspp/resource/base/resource.hpp index 025f85d9..f3ab8d2e 100644 --- a/src/rcspp/resource/base/resource.hpp +++ b/src/rcspp/resource/base/resource.hpp @@ -153,6 +153,11 @@ class Resource : public ResourceType { return dominance_function_->check_dominance(*this, rhs_resource); } + // Check distance from the resource to another + auto is_lower(const Resource& rhs_resource, double delta = 0) const -> bool { + return dominance_function_->fast_check_dominance(*this, rhs_resource, delta); + } + // Return resource cost [[nodiscard]] auto get_cost() const -> double { return cost_function_->get_cost(*this); } diff --git a/src/rcspp/resource/composition/functions/dominance/component_dominance_function.hpp b/src/rcspp/resource/composition/functions/dominance/component_dominance_function.hpp index c6f8f763..fc46947b 100644 --- a/src/rcspp/resource/composition/functions/dominance/component_dominance_function.hpp +++ b/src/rcspp/resource/composition/functions/dominance/component_dominance_function.hpp @@ -29,6 +29,15 @@ class ComponentDominanceFunction return lhs_component_resource <= rhs_component_resource; } + // Use to check (partial) dominance quickly. Useful for more complex data structure + bool fast_check_dominance( + const Resource>& lhs_resource, + const Resource>& rhs_resource, + double delta) override { + throw std::logic_error( + "ComponentDominanceFunction::fast_check_dominance() not implemented."); + } + private: size_t resource_index_; }; diff --git a/src/rcspp/resource/composition/functions/dominance/composition_dominance_function.hpp b/src/rcspp/resource/composition/functions/dominance/composition_dominance_function.hpp index d4449896..bfe42248 100644 --- a/src/rcspp/resource/composition/functions/dominance/composition_dominance_function.hpp +++ b/src/rcspp/resource/composition/functions/dominance/composition_dominance_function.hpp @@ -34,6 +34,15 @@ class CompositionDominanceFunction lhs_resource.get_resource_components()); } + // Use to check (partial) dominance quickly. Useful for more complex data structure + bool fast_check_dominance( + const Resource>& lhs_resource, + const Resource>& rhs_resource, + double delta) override { + throw std::logic_error( + "ComponentDominanceFunction::fast_check_dominance() not implemented."); + } + private: bool check_dominance(const auto& lhs_sing_res_vec, const auto& rhs_sing_res_vec) { for (int i = 0; i < lhs_sing_res_vec.size(); i++) { diff --git a/src/rcspp/resource/concrete/container_resource.hpp b/src/rcspp/resource/concrete/container_resource.hpp index ee1752eb..2fa7e945 100644 --- a/src/rcspp/resource/concrete/container_resource.hpp +++ b/src/rcspp/resource/concrete/container_resource.hpp @@ -165,6 +165,7 @@ class BitsetResource : public ContainerResource, BitsetRes for (auto idx : indices) { add(idx); } + size_ = indices.size(); } // convenience setter from an index set @@ -174,11 +175,13 @@ class BitsetResource : public ContainerResource, BitsetRes for (auto idx : indices) { add(idx); } + size_ = indices.size(); } void set_value(Container container) override { ContainerResource, BitsetResource, T>::set_value( std::move(container)); + compute_size(); } // Note: idx >> 6 is a bitwise right shift of idx by 6 bits — equivalent to integer division @@ -187,6 +190,7 @@ class BitsetResource : public ContainerResource, BitsetRes void add(const ValueType& idx) override { ensure_size(idx + 1); this->container_[idx >> 6] |= (1ULL << (idx & 63)); // NOLINT + ++size_; } void add(const Container& other_words) override { @@ -196,12 +200,14 @@ class BitsetResource : public ContainerResource, BitsetRes for (size_t i = 0; i < other_words_count; ++i) { this->container_[i] |= other_words[i]; } + compute_size(); } void remove(const ValueType& idx) override { if (idx < 64 * this->container_.size()) { // avoid out-of-bounds NOLINT this->container_[idx >> 6] &= ~(1ULL << (idx & 63)); // NOLINT } + --size_; } [[nodiscard]] bool contains(const ValueType& idx) const override { @@ -292,12 +298,13 @@ class BitsetResource : public ContainerResource, BitsetRes [[nodiscard]] const Container& words() const { return this->container_; } - [[nodiscard]] size_t size() const override { - size_t ones_cnt = 0; + [[nodiscard]] size_t size() const override { return size_; } + + void compute_size() { + size_ = 0; for (const uint64_t w : this->container_) { - ones_cnt += static_cast(std::popcount(w)); + size_ += static_cast(std::popcount(w)); } - return ones_cnt; } [[nodiscard]] bool empty() const override { @@ -324,6 +331,7 @@ class BitsetResource : public ContainerResource, BitsetRes private: // storage is inherited from ContainerResource as `container_`. + size_t size_; void ensure_size(ValueType requested_nb_bits) { const size_t new_words = (requested_nb_bits + 63) / 64; diff --git a/src/rcspp/resource/concrete/functions/dominance/contain_dominance_function.hpp b/src/rcspp/resource/concrete/functions/dominance/contain_dominance_function.hpp index 352cc638..a5567b8b 100644 --- a/src/rcspp/resource/concrete/functions/dominance/contain_dominance_function.hpp +++ b/src/rcspp/resource/concrete/functions/dominance/contain_dominance_function.hpp @@ -8,14 +8,21 @@ namespace rcspp { -template -class ContainDominanceFunction : public Clonable, - DominanceFunction> { - public: - auto check_dominance(const Resource& lhs_resource, - const Resource& rhs_resource) -> bool override { - // lhs_resource dominates rhs_resource if lhs_resource contains rhs_resource - return lhs_resource.includes(rhs_resource.get_value()); - } -}; + template + class ContainDominanceFunction + : public Clonable, DominanceFunction> { + public: + auto check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource) -> bool override { + // lhs_resource dominates rhs_resource if lhs_resource <= rhs_resource + // i.e., if lhs_resource contains rhs_resource + return lhs_resource.includes(rhs_resource.get_value()); + } + + auto fast_check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource, double delta) + -> bool override { + return rhs_resource.size() <= lhs_resource.size() + delta; + } + }; } // namespace rcspp diff --git a/src/rcspp/resource/concrete/functions/dominance/inclusion_dominance_function.hpp b/src/rcspp/resource/concrete/functions/dominance/inclusion_dominance_function.hpp index 7ef8d0ab..c2c6fae4 100644 --- a/src/rcspp/resource/concrete/functions/dominance/inclusion_dominance_function.hpp +++ b/src/rcspp/resource/concrete/functions/dominance/inclusion_dominance_function.hpp @@ -19,5 +19,11 @@ class InclusionDominanceFunction // i.e., if rhs_resource includes lhs_resource return rhs_resource.includes(lhs_resource.get_value()); } + + auto fast_check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource, double delta) + -> bool override { + return lhs_resource.size() <= rhs_resource.size() + delta; + } }; } // namespace rcspp diff --git a/src/rcspp/resource/concrete/functions/dominance/value_dominance_function.hpp b/src/rcspp/resource/concrete/functions/dominance/value_dominance_function.hpp index 5ad48480..7314d1c7 100644 --- a/src/rcspp/resource/concrete/functions/dominance/value_dominance_function.hpp +++ b/src/rcspp/resource/concrete/functions/dominance/value_dominance_function.hpp @@ -23,5 +23,11 @@ class ValueDominanceFunction const Resource& rhs_resource) -> bool override { return lhs_resource.leq(rhs_resource.get_value()); } + + auto fast_check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource, double delta) + -> bool override { + return lhs_resource.leq(rhs_resource.get_value() + delta); + } }; } // namespace rcspp diff --git a/src/rcspp/resource/functions/dominance/dominance_function.hpp b/src/rcspp/resource/functions/dominance/dominance_function.hpp index 4679fca5..59eda66a 100644 --- a/src/rcspp/resource/functions/dominance/dominance_function.hpp +++ b/src/rcspp/resource/functions/dominance/dominance_function.hpp @@ -19,6 +19,11 @@ class DominanceFunction { virtual auto check_dominance(const Resource& lhs_resource, const Resource& rhs_resource) -> bool = 0; + // Use to check (partial) dominance quickly. Useful for more complex data structure + virtual auto fast_check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource, double delta) + -> bool = 0; + [[nodiscard]] virtual auto clone() const -> std::unique_ptr = 0; auto create(const size_t node_id) -> std::unique_ptr { diff --git a/src/rcspp/resource/functions/dominance/trivial_dominance_function.hpp b/src/rcspp/resource/functions/dominance/trivial_dominance_function.hpp index c2cc0e8f..5ea2606c 100644 --- a/src/rcspp/resource/functions/dominance/trivial_dominance_function.hpp +++ b/src/rcspp/resource/functions/dominance/trivial_dominance_function.hpp @@ -16,5 +16,12 @@ class TrivialDominanceFunction const Resource& rhs_resource) override { return true; } + + // Use to check (partial) dominance quickly. Useful for more complex data structure + bool fast_check_dominance(const Resource& lhs_resource, + const Resource& rhs_resource, + double delta) override { + return true; + } }; } // namespace rcspp diff --git a/src/rcspp/resource/resource_graph.hpp b/src/rcspp/resource/resource_graph.hpp index a2991c1f..34ca2994 100644 --- a/src/rcspp/resource/resource_graph.hpp +++ b/src/rcspp/resource/resource_graph.hpp @@ -28,15 +28,13 @@ namespace rcspp { template class ResourceGraph : public Graph> { + using RComp = ResourceComposition; + public: - ResourceGraph( - std::unique_ptr>> - extension_function, - std::unique_ptr>> - feasibility_function, - std::unique_ptr>> cost_function, - std::unique_ptr>> - dominance_function) + ResourceGraph(std::unique_ptr> extension_function, + std::unique_ptr> feasibility_function, + std::unique_ptr> cost_function, + std::unique_ptr> dominance_function) : resource_factory_(ResourceCompositionFactory( std::move(extension_function), std::move(feasibility_function), std::move(cost_function), std::move(dominance_function))), @@ -96,28 +94,25 @@ class ResourceGraph : public Graph> { resource_base_prototype)); } - Node>& add_node(size_t node_id, bool source = false, - bool sink = false) override { - auto& node = - Graph>::add_node(node_id, source, sink); + Node& add_node(size_t node_id, bool source = false, bool sink = false) override { + auto& node = Graph::add_node(node_id, source, sink); node.resource = resource_factory_.make_resource(node.id); return node; } - Node>& add_node( + Node& add_node( size_t node_id, const std::tuple>...>& resource_initializer, bool source = false, bool sink = false) { - auto& node = - Graph>::add_node(node_id, source, sink); + auto& node = Graph::add_node(node_id, source, sink); node.resource = resource_factory_.make_resource(node.id, resource_initializer); return node; } template - Node>& add_node( + Node& add_node( size_t node_id, const std::tuple...>& resource_init_values, @@ -140,17 +135,14 @@ class ResourceGraph : public Graph> { return add_node(node_id, resource_initializer, source, sink); } - Arc>& add_arc( + Arc& add_arc( const std::tuple>...>& resource_consumption, size_t origin_node_id, size_t destination_node_id, std::optional arc_id = std::nullopt, double cost = 0.0, std::vector dual_rows = {}) { - auto& arc = Graph>::add_arc(origin_node_id, - destination_node_id, - arc_id, - cost, - dual_rows); + auto& arc = + Graph::add_arc(origin_node_id, destination_node_id, arc_id, cost, dual_rows); auto resource_base = resource_factory_ @@ -162,7 +154,7 @@ class ResourceGraph : public Graph> { } template - Arc>& add_arc( + Arc& add_arc( const std::tuple...>& extender_resource_consumption, size_t origin_node_id, size_t destination_node_id, @@ -198,7 +190,7 @@ class ResourceGraph : public Graph> { } void update_arc( - Arc>* arc, + Arc* arc, const std::tuple>...>& resource_consumption, std::optional cost = std::nullopt) { @@ -211,7 +203,7 @@ class ResourceGraph : public Graph> { template void update_arc( - Arc>* arc, std::size_t resource_index, + Arc* arc, std::size_t resource_index, const ResourceInitializerTypeTuple_t& single_resource_consumption, std::optional cost = std::nullopt) { constexpr size_t ResourceTypeIndex = @@ -238,25 +230,37 @@ class ResourceGraph : public Graph> { } template