From 7d0438b689439bfc6515e98776363c6852483f14 Mon Sep 17 00:00:00 2001 From: Masataro Asai Date: Wed, 15 Jan 2025 00:45:42 -0500 Subject: [PATCH 1/3] Softmin-type(h) (Kuroiwa & Beck 2022) ported from https://github.com/Kurorororo/biased-exploration --- src/search/CMakeLists.txt | 10 + .../open_lists/softmin_heap_open_list.cc | 278 +++++++++++++++++ .../open_lists/softmin_heap_open_list.h | 30 ++ src/search/open_lists/softmin_open_list.cc | 253 ++++++++++++++++ src/search/open_lists/softmin_open_list.h | 29 ++ .../softmin_type_based_open_list.cc | 283 ++++++++++++++++++ .../open_lists/softmin_type_based_open_list.h | 28 ++ 7 files changed, 911 insertions(+) create mode 100644 src/search/open_lists/softmin_heap_open_list.cc create mode 100644 src/search/open_lists/softmin_heap_open_list.h create mode 100644 src/search/open_lists/softmin_open_list.cc create mode 100644 src/search/open_lists/softmin_open_list.h create mode 100644 src/search/open_lists/softmin_type_based_open_list.cc create mode 100644 src/search/open_lists/softmin_type_based_open_list.h diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt index 2ed853265b..edcbbebca0 100644 --- a/src/search/CMakeLists.txt +++ b/src/search/CMakeLists.txt @@ -201,6 +201,16 @@ create_fast_downward_library( open_lists/type_based_open_list ) + +create_fast_downward_library( + NAME SOFTMIN_OPEN_LIST + HELP "Open list that randomly selects h with a probability that is a softmax over different h values. (Kuroiwa and Beck, ICAPS2022)" + SOURCES + open_lists/softmin_open_list + open_lists/softmin_heap_open_list + open_lists/softmin_type_based_open_list +) + create_fast_downward_library( NAME dynamic_bitset HELP "Poor man's version of boost::dynamic_bitset" diff --git a/src/search/open_lists/softmin_heap_open_list.cc b/src/search/open_lists/softmin_heap_open_list.cc new file mode 100644 index 0000000000..7726ce7a14 --- /dev/null +++ b/src/search/open_lists/softmin_heap_open_list.cc @@ -0,0 +1,278 @@ +#include "softmin_heap_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace softmin_heap_open_list { +template +class SoftminHeapOpenList : public OpenList { + struct HeapNode { + int id; + Entry entry; + HeapNode(int id, const Entry &entry) + : id(id), entry(entry) { + } + + bool operator>(const HeapNode &other) const { + return id > other.id; + } + }; + + typedef vector Bucket; + + utils::RandomNumberGenerator rng; + map buckets; + int size; + double tau; + bool ignore_size; + bool ignore_weights; + double epsilon; + bool only_tie_breaking; + int next_id; + double current_sum; + + shared_ptr evaluator; + +protected: + virtual void do_insertion(EvaluationContext &eval_context, + const Entry &entry) override; + +public: + explicit SoftminHeapOpenList(const plugins::Options &opts); + SoftminHeapOpenList(const shared_ptr &eval, bool preferred_only); + virtual ~SoftminHeapOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual void get_path_dependent_evaluators(set &evals) override; + virtual bool is_dead_end( + EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; +}; + +template +static void adjust_heap_up(vector &heap, size_t pos) { + assert(utils::in_bounds(pos, heap)); + while (pos != 0) { + size_t parent_pos = (pos - 1) / 2; + if (heap[pos] > heap[parent_pos]) { + break; + } + swap(heap[pos], heap[parent_pos]); + pos = parent_pos; + } +} + + +template +SoftminHeapOpenList::SoftminHeapOpenList(const plugins::Options &opts) + : OpenList(opts.get("pref_only")), + rng(opts.get("random_seed")), + size(0), + tau(opts.get("tau")), + ignore_size(opts.get("ignore_size")), + ignore_weights(opts.get("ignore_weights")), + epsilon(opts.get("epsilon")), + only_tie_breaking(opts.get("only_tie_breaking")), + next_id(0), + current_sum(0.0), + evaluator(opts.get>("eval")) { +} + +template +void SoftminHeapOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + int key = eval_context.get_evaluator_value(evaluator.get()); + + if (ignore_size) { + if (buckets.find(key) == buckets.end()) { + if (ignore_weights) + current_sum += 1; + else + current_sum += std::exp(-1.0 * static_cast(key) / tau); + } + } else { + if (ignore_weights) + current_sum += 1; + else + current_sum += std::exp(-1.0 * static_cast(key) / tau); + + } + + buckets[key].emplace_back(next_id++, entry); + push_heap(buckets[key].begin(), buckets[key].end(), greater()); + ++size; +} + +template +Entry SoftminHeapOpenList::remove_min() { + assert(size > 0); + int key = buckets.begin()->first; + bool random_tie_breaking = false; + + double r = rng.random(); + if (r <= epsilon) { + random_tie_breaking = true; + if (!only_tie_breaking && buckets.size() > 1) { + double p_sum = 0.0; + double total_sum = current_sum; + if (!ignore_weights || !ignore_size) { + double tmp_sum = 0; + for (auto it : buckets) { + double v = 0; + if (ignore_size) + v = 1; + else + v = it.second.size(); + if (!ignore_weights) v *= std::exp(-1.0 * static_cast(it.first) / tau); + tmp_sum += v; + } + total_sum = tmp_sum; + } + for (auto it : buckets) { + double p = 1.0 / total_sum; + if (!ignore_weights) p *= std::exp(-1.0 * static_cast(it.first) / tau); + if (!ignore_size) p *= static_cast(it.second.size()); + p_sum += p; + if (r <= p_sum * epsilon) { + key = it.first; + break; + } + } + } + } + + Bucket &bucket = buckets[key]; + assert(!bucket.empty()); + + if (random_tie_breaking) { + int pos = rng.random(bucket.size()); + bucket[pos].id = numeric_limits::min(); + adjust_heap_up(bucket, pos); + } + + pop_heap(bucket.begin(), bucket.end(), greater()); + HeapNode heap_node = bucket.back(); + bucket.pop_back(); + + if (bucket.empty()) { + buckets.erase(key); + if (ignore_size) { + if (ignore_weights) + current_sum -= 1; + else + current_sum -= std::exp(-1.0 * static_cast(key) / tau); + } + } + if (!ignore_size) { + if (ignore_weights) + current_sum -= 1; + else + current_sum -= std::exp(-1.0 * static_cast(key) / tau); + } + --size; + return heap_node.entry; +} + +template +bool SoftminHeapOpenList::empty() { + return size == 0; +} + +template +void SoftminHeapOpenList::clear() { + buckets.clear(); + size = 0; +} + +template +void SoftminHeapOpenList::get_path_dependent_evaluators( + set &evals) { + evaluator->get_path_dependent_evaluators(evals); +} + +template +bool SoftminHeapOpenList::is_dead_end( + EvaluationContext &eval_context) const { + return eval_context.is_evaluator_value_infinite(evaluator.get()); +} + +template +bool SoftminHeapOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); +} + +SoftminHeapOpenListFactory::SoftminHeapOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +SoftminHeapOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +SoftminHeapOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class SoftminHeapOpenListFeature : public plugins::TypedFeature { +public: + SoftminHeapOpenListFeature(): TypedFeature("sofmin_heap") { + document_title("SoftminHeap open list"); + document_synopsis( + "Open list that uses a single evaluator and FIFO tiebreaking."); + document_note( + "Implementation Notes", + "Elements with the same evaluator value are stored in double-ended " + "queues, called \"buckets\". The open list stores a map from evaluator " + "values to buckets. Pushing and popping from a bucket runs in constant " + "time. Therefore, inserting and removing an entry from the open list " + "takes time O(log(n)), where n is the number of buckets."); + add_option>("eval", "evaluator"); + add_option( + "pref_only", + "insert only nodes generated by preferred operators", "false"); + add_option( + "tau", + "temperature parameter of softmin_heap", "1.0"); + add_option( + "ignore_size", + "ignore bucket sizes", "false"); + add_option( + "ignore_weights", + "ignore weights of buckets", "false"); + add_option( + "epsilon", + "probability for choosing the next entry randomly", + "1.0", + plugins::Bounds("0.0", "1.0")); + add_option( + "only_tie_breaking", + "just randomize tie-breaking", "false"); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + + diff --git a/src/search/open_lists/softmin_heap_open_list.h b/src/search/open_lists/softmin_heap_open_list.h new file mode 100644 index 0000000000..83f4ad330f --- /dev/null +++ b/src/search/open_lists/softmin_heap_open_list.h @@ -0,0 +1,30 @@ +#ifndef OPEN_LISTS_SOFTMIN_HEAP_OPEN_LIST_H +#define OPEN_LISTS_SOFTMIN_HEAP_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + +/* + Softmin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is a softmax over different h values. + + This is a heap-based implementation for a single evaluator. +*/ + +namespace softmin_heap_open_list { +class SoftminHeapOpenListFactory : public OpenListFactory { + plugins::Options options; + + public: + explicit SoftminHeapOpenListFactory(const plugins::Options &options); + virtual ~SoftminHeapOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} // namespace exploraive_open_list + +#endif + + diff --git a/src/search/open_lists/softmin_open_list.cc b/src/search/open_lists/softmin_open_list.cc new file mode 100644 index 0000000000..4969065dd7 --- /dev/null +++ b/src/search/open_lists/softmin_open_list.cc @@ -0,0 +1,253 @@ +#include "softmin_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace softmin_open_list { +template +class SoftminOpenList : public OpenList { + typedef deque Bucket; + + utils::RandomNumberGenerator rng; + map buckets; + int size; + double tau; + bool ignore_size; + bool ignore_weights; + bool relative_h; + int relative_h_offset; + double epsilon; + double current_sum; + + shared_ptr evaluator; + +protected: + virtual void do_insertion(EvaluationContext &eval_context, + const Entry &entry) override; + +public: + explicit SoftminOpenList(const plugins::Options &opts); + SoftminOpenList(const shared_ptr &eval, bool preferred_only); + virtual ~SoftminOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual void get_path_dependent_evaluators(set &evals) override; + virtual bool is_dead_end( + EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; +}; + + +template +SoftminOpenList::SoftminOpenList(const plugins::Options &opts) + : OpenList(opts.get("pref_only")), + rng(opts.get("random_seed")), + size(0), + tau(opts.get("tau")), + ignore_size(opts.get("ignore_size")), + ignore_weights(opts.get("ignore_weights")), + relative_h(opts.get("relative_h")), + relative_h_offset(opts.get("relative_h_offset")), + epsilon(opts.get("epsilon")), + current_sum(0.0), + evaluator(opts.get>("eval")) { +} + +template +void SoftminOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + int key = eval_context.get_evaluator_value(evaluator.get()); + + if (ignore_size) { + if (buckets.find(key) == buckets.end()) { + if (ignore_weights) + current_sum += 1; + else if (!relative_h) + current_sum += std::exp(-1.0 * static_cast(key) / tau); + } + } else { + if (ignore_weights) + current_sum += 1; + else if (!relative_h) + current_sum += std::exp(-1.0 * static_cast(key) / tau); + + } + + buckets[key].push_back(entry); + ++size; +} + +template +Entry SoftminOpenList::remove_min() { + assert(size > 0); + int key = buckets.begin()->first; + + if (buckets.size() > 1) { + double r = rng.random(); + if (r <= epsilon) { + if (relative_h) { + double total_sum = 0; + int i = relative_h_offset; + for (auto it : buckets) { + double s = std::exp(-1.0 * static_cast(i) / tau); + if (!ignore_size) s *= static_cast(it.second.size()); + total_sum += s; + ++i; + } + double p_sum = 0.0; + i = relative_h_offset; + for (auto it : buckets) { + double p = std::exp(-1.0 * static_cast(i) / tau) / total_sum; + if (!ignore_size) p *= static_cast(it.second.size()); + p_sum += p; + ++i; + if (r <= p_sum * epsilon) { + key = it.first; + break; + } + } + } else { + double total_sum = current_sum; + double p_sum = 0.0; + for (auto it : buckets) { + double p = 1.0 / total_sum; + if (!ignore_weights) p *= std::exp(-1.0 * static_cast(it.first) / tau); + if (!ignore_size) p *= static_cast(it.second.size()); + p_sum += p; + if (r <= p_sum * epsilon) { + key = it.first; + break; + } + } + } + } + } + + Bucket &bucket = buckets[key]; + assert(!bucket.empty()); + Entry result = bucket.front(); + bucket.pop_front(); + if (bucket.empty()) { + buckets.erase(key); + if (ignore_size) { + if (ignore_weights) + current_sum -= 1; + else if (!relative_h) + current_sum -= std::exp(-1.0 * static_cast(key) / tau); + } + } + if (!ignore_size) { + if (ignore_weights) + current_sum -= 1; + else if (!relative_h) + current_sum -= std::exp(-1.0 * static_cast(key) / tau); + } + --size; + return result; +} + +template +bool SoftminOpenList::empty() { + return size == 0; +} + +template +void SoftminOpenList::clear() { + buckets.clear(); + size = 0; +} + +template +void SoftminOpenList::get_path_dependent_evaluators( + set &evals) { + evaluator->get_path_dependent_evaluators(evals); +} + +template +bool SoftminOpenList::is_dead_end( + EvaluationContext &eval_context) const { + return eval_context.is_evaluator_value_infinite(evaluator.get()); +} + +template +bool SoftminOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); +} + +SoftminOpenListFactory::SoftminOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +SoftminOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +SoftminOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class SoftminOpenListFeature : public plugins::TypedFeature { +public: + SoftminOpenListFeature(): TypedFeature("softmin") { + document_title("Softmin open list"); + document_synopsis( + "Open list that uses a single evaluator and FIFO tiebreaking."); + document_note( + "Implementation Notes", + "Elements with the same evaluator value are stored in double-ended " + "queues, called \"buckets\". The open list stores a map from evaluator " + "values to buckets. Pushing and popping from a bucket runs in constant " + "time. Therefore, inserting and removing an entry from the open list " + "takes time O(log(n)), where n is the number of buckets."); + add_option>("eval", "evaluator"); + add_option( + "pref_only", + "insert only nodes generated by preferred operators", "false"); + add_option( + "tau", + "temperature parameter of softmin", "1.0"); + add_option( + "ignore_size", + "ignore bucket sizes", "false"); + add_option( + "ignore_weights", + "ignore weights of buckets", "false"); + add_option( + "relative_h", + "use relative positions of h-values", "false"); + add_option( + "relative_h_offset", + "starting value of relative h-values", "0"); + add_option( + "epsilon", + "probability for choosing the next entry randomly", + "1.0", + plugins::Bounds("0.0", "1.0")); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + diff --git a/src/search/open_lists/softmin_open_list.h b/src/search/open_lists/softmin_open_list.h new file mode 100644 index 0000000000..8645116592 --- /dev/null +++ b/src/search/open_lists/softmin_open_list.h @@ -0,0 +1,29 @@ +#ifndef OPEN_LISTS_SOFTMIN_OPEN_LIST_H +#define OPEN_LISTS_SOFTMIN_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + +/* + Softmin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is a softmax over different h values. + + This is a bucket-based implementation for a single evaluator. +*/ + +namespace softmin_open_list { +class SoftminOpenListFactory : public OpenListFactory { + plugins::Options options; + + public: + explicit SoftminOpenListFactory(const plugins::Options &options); + virtual ~SoftminOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} // namespace exploraive_open_list + +#endif + diff --git a/src/search/open_lists/softmin_type_based_open_list.cc b/src/search/open_lists/softmin_type_based_open_list.cc new file mode 100644 index 0000000000..9c2ce22f16 --- /dev/null +++ b/src/search/open_lists/softmin_type_based_open_list.cc @@ -0,0 +1,283 @@ +#include "softmin_type_based_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../utils/collections.h" +#include "../utils/hash.h" +#include "../utils/markup.h" +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace softmin_type_based_open_list { +template +class SoftminTypeBasedOpenList : public OpenList { + utils::RandomNumberGenerator rng; + vector> evaluators; + + using Key = vector; + using Bucket = vector; + unordered_map>> first_to_keys_and_buckets; + unordered_map> first_to_key_to_bucket_index; + std::set first_values; + + double tau; + bool ignore_size; + bool ignore_weights; + double current_sum; + +protected: + virtual void do_insertion( + EvaluationContext &eval_context, const Entry &entry) override; + +public: + explicit SoftminTypeBasedOpenList(const plugins::Options &opts); + virtual ~SoftminTypeBasedOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual bool is_dead_end(EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; + virtual void get_path_dependent_evaluators(set &evals) override; +}; + +template +void SoftminTypeBasedOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + vector key; + key.reserve(evaluators.size() - 1); + bool first = true; + int key_first = -1; + for (const shared_ptr &evaluator : evaluators) { + if (first) { + key_first = eval_context.get_evaluator_value_or_infinity(evaluator.get()); + first = false; + } else { + key.push_back( + eval_context.get_evaluator_value_or_infinity(evaluator.get())); + } + } + + auto first_it = first_to_key_to_bucket_index.find(key_first); + if (first_it == first_to_key_to_bucket_index.end()) { + first_values.insert(key_first); + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + first_to_key_to_bucket_index[key_first][key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + + if (ignore_size) { + if (ignore_weights) + current_sum += 1; + else + current_sum += std::exp(-1.0 * static_cast(key_first) / tau); + } + } else { + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + auto it = key_to_bucket_index.find(key); + if (it == key_to_bucket_index.end()) { + key_to_bucket_index[key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + } else { + size_t bucket_index = it->second; + assert(utils::in_bounds(bucket_index, keys_and_buckets)); + keys_and_buckets[bucket_index].second.push_back(entry); + } + } + + if (!ignore_size) { + if (ignore_weights) { + current_sum += 1; + } else { + current_sum += std::exp(-1.0 * static_cast(key_first) / tau); + } + } +} + +template +SoftminTypeBasedOpenList::SoftminTypeBasedOpenList(const plugins::Options &opts) + : rng(opts.get("random_seed")), + evaluators(opts.get_list>("evaluators")), + tau(opts.get("tau")), + ignore_size(opts.get("ignore_size")), + ignore_weights(opts.get("ignore_weights")), + current_sum(0.0) { +} + +template +Entry SoftminTypeBasedOpenList::remove_min() { + int key_first = *first_values.begin(); + if (first_values.size() > 1) { + double r = rng.random(); + double p_sum = 0.0; + + for (auto value : first_values) { + double p = 1.0 / current_sum; + + if (!ignore_weights) + p *= std::exp(-1.0 * static_cast(value) / tau); + + if (!ignore_size) + p *= static_cast(first_to_keys_and_buckets[value].size()); + + p_sum += p; + if (r <= p_sum) { + key_first = value; + break; + } + } + } + + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + + size_t bucket_id = rng.random(keys_and_buckets.size()); + auto &key_and_bucket = keys_and_buckets[bucket_id]; + const Key &min_key = key_and_bucket.first; + Bucket &bucket = key_and_bucket.second; + int pos = rng.random(bucket.size()); + Entry result = utils::swap_and_pop_from_vector(bucket, pos); + + if (bucket.empty()) { + // Swap the empty bucket with the last bucket, then delete it. + key_to_bucket_index[keys_and_buckets.back().first] = bucket_id; + key_to_bucket_index.erase(min_key); + utils::swap_and_pop_from_vector(keys_and_buckets, bucket_id); + + if (keys_and_buckets.empty()) { + first_to_keys_and_buckets.erase(key_first); + first_to_key_to_bucket_index.erase(key_first); + first_values.erase(key_first); + + if (ignore_size) { + if (ignore_weights) { + current_sum -= 1; + } else { + current_sum -= std::exp(-1.0 * static_cast(key_first) / tau); + } + } + } + } + + if (!ignore_size) { + if (ignore_weights) { + current_sum -= 1; + } else { + current_sum -= std::exp(-1.0 * static_cast(key_first) / tau); + } + } + + return result; +} + +template +bool SoftminTypeBasedOpenList::empty() { + return first_values.empty(); +} + +template +void SoftminTypeBasedOpenList::clear() { + first_to_keys_and_buckets.clear(); + first_to_key_to_bucket_index.clear(); + first_to_keys_and_buckets.clear(); +} + +template +bool SoftminTypeBasedOpenList::is_dead_end( + EvaluationContext &eval_context) const { + // If one evaluator is sure we have a dead end, return true. + if (is_reliable_dead_end(eval_context)) + return true; + // Otherwise, return true if all evaluators agree this is a dead-end. + for (const shared_ptr &evaluator : evaluators) { + if (!eval_context.is_evaluator_value_infinite(evaluator.get())) + return false; + } + return true; +} + +template +bool SoftminTypeBasedOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable() && + eval_context.is_evaluator_value_infinite(evaluator.get())) + return true; + } + return false; +} + +template +void SoftminTypeBasedOpenList::get_path_dependent_evaluators( + set &evals) { + for (const shared_ptr &evaluator : evaluators) { + evaluator->get_path_dependent_evaluators(evals); + } +} + +SoftminTypeBasedOpenListFactory::SoftminTypeBasedOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +SoftminTypeBasedOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +SoftminTypeBasedOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class SoftminTypeBasedOpenListFeature : public plugins::TypedFeature { +public: + SoftminTypeBasedOpenListFeature(): TypedFeature("softmin_type_based"){ + document_title("SoftminType-based open list"); + document_synopsis( + "Uses multiple evaluators to assign entries to buckets. " + "All entries in a bucket have the same evaluator values. " + "When retrieving an entry, a bucket is chosen uniformly at " + "random and one of the contained entries is selected " + "uniformly randomly. " + "The algorithm is based on" + utils::format_conference_reference( + {"Fan Xie", "Martin Mueller", "Robert Holte", "Tatsuya Imai"}, + "SoftminType-Based Exploration with Multiple Search Queues for" + " Satisficing Planning", + "http://www.aaai.org/ocs/index.php/AAAI/AAAI14/paper/view/8472/8705", + "Proceedings of the Twenty-Eigth AAAI Conference Conference" + " on Artificial Intelligence (AAAI 2014)", + "2395-2401", + "AAAI Press", + "2014")); + add_list_option>( + "evaluators", + "Evaluators used to determine the bucket for each entry."); + add_option( + "tau", + "temperature parameter of softmin", "1.0"); + add_option( + "ignore_size", + "ignore size of second to last keys", "false"); + add_option( + "ignore_weights", + "ignore softmin weights", "false"); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + diff --git a/src/search/open_lists/softmin_type_based_open_list.h b/src/search/open_lists/softmin_type_based_open_list.h new file mode 100644 index 0000000000..6f47fd6f09 --- /dev/null +++ b/src/search/open_lists/softmin_type_based_open_list.h @@ -0,0 +1,28 @@ +#ifndef OPEN_LISTS_SOFTMIN_TYPE_BASED_OPEN_LIST_H +#define OPEN_LISTS_SOFTMIN_TYPE_BASED_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + +/* + Lin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is a softmax over different h values. + + This is an implementation for multiple evaluators. +*/ + +namespace softmin_type_based_open_list { +class SoftminTypeBasedOpenListFactory : public OpenListFactory { + plugins::Options options; +public: + explicit SoftminTypeBasedOpenListFactory(const plugins::Options &options); + virtual ~SoftminTypeBasedOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} + +#endif + From f3d02b4b64d4e2fba130ed324d5b361c9dbd5091 Mon Sep 17 00:00:00 2001 From: Masataro Asai Date: Wed, 15 Jan 2025 00:46:17 -0500 Subject: [PATCH 2/3] Lin-type(h) (Kuroiwa & Beck 2022) ported from https://github.com/Kurorororo/biased-exploration --- src/search/CMakeLists.txt | 9 + .../linear_weighted_heap_open_list.cc | 248 ++++++++++++++++ .../linear_weighted_heap_open_list.h | 32 +++ .../open_lists/linear_weighted_open_list.cc | 191 +++++++++++++ .../open_lists/linear_weighted_open_list.h | 30 ++ .../linear_weighted_type_based_open_list.cc | 266 ++++++++++++++++++ .../linear_weighted_type_based_open_list.h | 29 ++ 7 files changed, 805 insertions(+) create mode 100644 src/search/open_lists/linear_weighted_heap_open_list.cc create mode 100644 src/search/open_lists/linear_weighted_heap_open_list.h create mode 100644 src/search/open_lists/linear_weighted_open_list.cc create mode 100644 src/search/open_lists/linear_weighted_open_list.h create mode 100644 src/search/open_lists/linear_weighted_type_based_open_list.cc create mode 100644 src/search/open_lists/linear_weighted_type_based_open_list.h diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt index edcbbebca0..37c437ba2a 100644 --- a/src/search/CMakeLists.txt +++ b/src/search/CMakeLists.txt @@ -211,6 +211,15 @@ create_fast_downward_library( open_lists/softmin_type_based_open_list ) +create_fast_downward_library( + NAME LINEAR_WEIGHTED_OPEN_LIST + HELP "Open list that randomly selects h with a probability that is linearly interpolated between max/min h. (Kuroiwa and Beck, ICAPS2022)" + SOURCES + open_lists/linear_weighted_open_list + open_lists/linear_weighted_heap_open_list + open_lists/linear_weighted_type_based_open_list +) + create_fast_downward_library( NAME dynamic_bitset HELP "Poor man's version of boost::dynamic_bitset" diff --git a/src/search/open_lists/linear_weighted_heap_open_list.cc b/src/search/open_lists/linear_weighted_heap_open_list.cc new file mode 100644 index 0000000000..225da91fd9 --- /dev/null +++ b/src/search/open_lists/linear_weighted_heap_open_list.cc @@ -0,0 +1,248 @@ +#include "linear_weighted_heap_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace linear_weighted_heap_open_list { +template +class LinearWeightedHeapOpenList : public OpenList { + struct HeapNode { + int id; + Entry entry; + HeapNode(int id, const Entry &entry) + : id(id), entry(entry) { + } + + bool operator>(const HeapNode &other) const { + return id > other.id; + } + }; + + typedef vector Bucket; + + utils::RandomNumberGenerator rng; + map buckets; + int size; + double alpha; + double beta; + bool ignore_size; + bool ignore_weights; + double epsilon; + bool only_tie_breaking; + int next_id; + + shared_ptr evaluator; + +protected: + virtual void do_insertion(EvaluationContext &eval_context, + const Entry &entry) override; + +public: + explicit LinearWeightedHeapOpenList(const plugins::Options &opts); + LinearWeightedHeapOpenList(const shared_ptr &eval, bool preferred_only); + virtual ~LinearWeightedHeapOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual void get_path_dependent_evaluators(set &evals) override; + virtual bool is_dead_end( + EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; +}; + +template +static void adjust_heap_up(vector &heap, size_t pos) { + assert(utils::in_bounds(pos, heap)); + while (pos != 0) { + size_t parent_pos = (pos - 1) / 2; + if (heap[pos] > heap[parent_pos]) { + break; + } + swap(heap[pos], heap[parent_pos]); + pos = parent_pos; + } +} + + +template +LinearWeightedHeapOpenList::LinearWeightedHeapOpenList(const plugins::Options &opts) + : OpenList(opts.get("pref_only")), + rng(opts.get("random_seed")), + size(0), + alpha(opts.get("alpha")), + beta(opts.get("beta")), + ignore_size(opts.get("ignore_size")), + ignore_weights(opts.get("ignore_weights")), + epsilon(opts.get("epsilon")), + only_tie_breaking(opts.get("only_tie_breaking")), + next_id(0), + evaluator(opts.get>("eval")) { +} + +template +void LinearWeightedHeapOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + int key = eval_context.get_evaluator_value(evaluator.get()); + + buckets[key].emplace_back(next_id++, entry); + push_heap(buckets[key].begin(), buckets[key].end(), greater()); + ++size; +} + +template +Entry LinearWeightedHeapOpenList::remove_min() { + assert(size > 0); + int key = buckets.begin()->first; + bool random_tie_breaking = false; + + double r = rng.random(); + if (r <= epsilon) { + random_tie_breaking = true; + if (!only_tie_breaking && buckets.size() > 1) { + double bias = static_cast(buckets.rbegin()->first) + beta; + double p_sum = 0.0; + double total_sum = 0.0; + for (auto it : buckets) { + double v = 0; + if (ignore_size) + v = 1; + else + v = it.second.size(); + if (!ignore_weights) v *= -1 * alpha * static_cast(it.first) + bias; + total_sum += v; + } + for (auto it : buckets) { + double p = 1.0 / total_sum; + if (!ignore_weights) p *= -1 * alpha * static_cast(it.first) + bias; + if (!ignore_size) p *= static_cast(it.second.size()); + p_sum += p; + if (r <= p_sum * epsilon) { + key = it.first; + break; + } + } + } + } + + Bucket &bucket = buckets[key]; + assert(!bucket.empty()); + + if (random_tie_breaking) { + int pos = rng.random(bucket.size()); + bucket[pos].id = numeric_limits::min(); + adjust_heap_up(bucket, pos); + } + + pop_heap(bucket.begin(), bucket.end(), greater()); + HeapNode heap_node = bucket.back(); + bucket.pop_back(); + + if (bucket.empty()) buckets.erase(key); + + --size; + return heap_node.entry; +} + +template +bool LinearWeightedHeapOpenList::empty() { + return size == 0; +} + +template +void LinearWeightedHeapOpenList::clear() { + buckets.clear(); + size = 0; +} + +template +void LinearWeightedHeapOpenList::get_path_dependent_evaluators( + set &evals) { + evaluator->get_path_dependent_evaluators(evals); +} + +template +bool LinearWeightedHeapOpenList::is_dead_end( + EvaluationContext &eval_context) const { + return eval_context.is_evaluator_value_infinite(evaluator.get()); +} + +template +bool LinearWeightedHeapOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); +} + +LinearWeightedHeapOpenListFactory::LinearWeightedHeapOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +LinearWeightedHeapOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +LinearWeightedHeapOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class LinearWeightedHeapOpenListFeature : public plugins::TypedFeature { +public: + LinearWeightedHeapOpenListFeature() : TypedFeature("linear_weighted_heap") { + + document_title("LinearWeightedHeap open list"); + document_synopsis( + "Open list that uses a single evaluator and FIFO tiebreaking."); + document_note( + "Implementation Notes", + "Elements with the same evaluator value are stored in double-ended " + "queues, called \"buckets\". The open list stores a map from evaluator " + "values to buckets. Pushing and popping from a bucket runs in constant " + "time. Therefore, inserting and removing an entry from the open list " + "takes time O(log(n)), where n is the number of buckets."); + add_option>("eval", "evaluator"); + add_option( + "pref_only", + "insert only nodes generated by preferred operators", "false"); + add_option("alpha", "coefficent", "1.0"); + add_option("beta", "bias", "1.0"); + add_option( + "ignore_size", + "ignore bucket sizes", "false"); + add_option( + "ignore_weights", + "ignore weights of buckets", "false"); + add_option( + "epsilon", + "probability for choosing the next entry randomly", + "1.0", + plugins::Bounds("0.0", "1.0")); + add_option( + "only_tie_breaking", + "just randomize tie-breaking", "false"); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + + + diff --git a/src/search/open_lists/linear_weighted_heap_open_list.h b/src/search/open_lists/linear_weighted_heap_open_list.h new file mode 100644 index 0000000000..c6c88d77a6 --- /dev/null +++ b/src/search/open_lists/linear_weighted_heap_open_list.h @@ -0,0 +1,32 @@ +#ifndef OPEN_LISTS_LINEAR_WEIGHTED_HEAP_OPEN_LIST_H +#define OPEN_LISTS_LINEAR_WEIGHTED_HEAP_OPEN_LIST_H + +#include "../open_list_factory.h" + +#include "../plugins/options.h" + +/* + Lin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is linearly interpolated between max/min h. + + This is a heap-based implementation for a single evaluator. +*/ + +namespace linear_weighted_heap_open_list { +class LinearWeightedHeapOpenListFactory : public OpenListFactory { + plugins::Options options; + +public: + explicit LinearWeightedHeapOpenListFactory(const plugins::Options &options); + virtual ~LinearWeightedHeapOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} // namespace exploraive_open_list + +#endif + + + diff --git a/src/search/open_lists/linear_weighted_open_list.cc b/src/search/open_lists/linear_weighted_open_list.cc new file mode 100644 index 0000000000..8c1b9c4246 --- /dev/null +++ b/src/search/open_lists/linear_weighted_open_list.cc @@ -0,0 +1,191 @@ +#include "linear_weighted_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace linear_weighted_open_list { +template +class LinearWeightedOpenList : public OpenList { + typedef deque Bucket; + + utils::RandomNumberGenerator rng; + map buckets; + int size; + double alpha; + double beta; + bool ignore_size; + double epsilon; + + shared_ptr evaluator; + +protected: + virtual void do_insertion(EvaluationContext &eval_context, + const Entry &entry) override; + +public: + explicit LinearWeightedOpenList(const plugins::Options &opts); + LinearWeightedOpenList(const shared_ptr &eval, bool preferred_only); + virtual ~LinearWeightedOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual void get_path_dependent_evaluators(set &evals) override; + virtual bool is_dead_end( + EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; +}; + + +template +LinearWeightedOpenList::LinearWeightedOpenList(const plugins::Options &opts) + : OpenList(opts.get("pref_only")), + rng(opts.get("random_seed")), + size(0), + alpha(opts.get("alpha")), + beta(opts.get("beta")), + ignore_size(opts.get("ignore_size")), + epsilon(opts.get("epsilon")), + evaluator(opts.get>("eval")) { +} + +template +void LinearWeightedOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + int key = eval_context.get_evaluator_value(evaluator.get()); + + buckets[key].push_back(entry); + ++size; +} + +template +Entry LinearWeightedOpenList::remove_min() { + assert(size > 0); + int key = buckets.begin()->first; + + if (buckets.size() > 1) { + double r = rng.random(); + if (r <= epsilon) { + double total_sum = 0; + double bias = beta + alpha * static_cast(buckets.rbegin()->first); + for (auto it : buckets) { + double s = -1.0 * alpha * static_cast(it.first) + bias; + if (!ignore_size) s *= static_cast(it.second.size()); + total_sum += s; + } + double p_sum = 0.0; + for (auto it : buckets) { + double p = (-1.0 * alpha * static_cast(it.first) + bias) / total_sum; + if (!ignore_size) p *= static_cast(it.second.size()); + p_sum += p; + if (r <= p_sum * epsilon) { + key = it.first; + break; + } + } + } + } + + Bucket &bucket = buckets[key]; + assert(!bucket.empty()); + Entry result = bucket.front(); + bucket.pop_front(); + if (bucket.empty()) buckets.erase(key); + --size; + return result; +} + +template +bool LinearWeightedOpenList::empty() { + return size == 0; +} + +template +void LinearWeightedOpenList::clear() { + buckets.clear(); + size = 0; +} + +template +void LinearWeightedOpenList::get_path_dependent_evaluators( + set &evals) { + evaluator->get_path_dependent_evaluators(evals); +} + +template +bool LinearWeightedOpenList::is_dead_end( + EvaluationContext &eval_context) const { + return eval_context.is_evaluator_value_infinite(evaluator.get()); +} + +template +bool LinearWeightedOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); +} + +LinearWeightedOpenListFactory::LinearWeightedOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +LinearWeightedOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +LinearWeightedOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class LinearWeightedOpenListFeature : public plugins::TypedFeature { +public: + LinearWeightedOpenListFeature(): TypedFeature("linear_weighted") { + document_title("LinearWeighted open list"); + document_synopsis( + "Open list that uses a single evaluator and FIFO tiebreaking."); + document_note( + "Implementation Notes", + "Elements with the same evaluator value are stored in double-ended " + "queues, called \"buckets\". The open list stores a map from evaluator " + "values to buckets. Pushing and popping from a bucket runs in constant " + "time. Therefore, inserting and removing an entry from the open list " + "takes time O(log(n)), where n is the number of buckets."); + add_option>("eval", "evaluator"); + add_option( + "pref_only", + "insert only nodes generated by preferred operators", "false"); + add_option("alpha", "coefficent", "1.0"); + add_option("beta", "bias", "1.0"); + add_option( + "ignore_size", + "ignore bucket sizes", "false"); + add_option( + "epsilon", + "probability for choosing the next entry randomly", + "1.0", + plugins::Bounds("0.0", "1.0")); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + + diff --git a/src/search/open_lists/linear_weighted_open_list.h b/src/search/open_lists/linear_weighted_open_list.h new file mode 100644 index 0000000000..1ce7f652d8 --- /dev/null +++ b/src/search/open_lists/linear_weighted_open_list.h @@ -0,0 +1,30 @@ +#ifndef OPEN_LISTS_LINEAR_WEIGHTED_OPEN_LIST_H +#define OPEN_LISTS_LINEAR_WEIGHTED_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + +/* + Lin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is linearly interpolated between max/min h. + + This is a bucket-based implementation for a single evaluator. +*/ + +namespace linear_weighted_open_list { +class LinearWeightedOpenListFactory : public OpenListFactory { + plugins::Options options; + + public: + explicit LinearWeightedOpenListFactory(const plugins::Options &options); + virtual ~LinearWeightedOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} // namespace exploraive_open_list + +#endif + + diff --git a/src/search/open_lists/linear_weighted_type_based_open_list.cc b/src/search/open_lists/linear_weighted_type_based_open_list.cc new file mode 100644 index 0000000000..8248e8ed42 --- /dev/null +++ b/src/search/open_lists/linear_weighted_type_based_open_list.cc @@ -0,0 +1,266 @@ +#include "linear_weighted_type_based_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../utils/collections.h" +#include "../utils/hash.h" +#include "../utils/markup.h" +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace linear_weighted_type_based_open_list { +template +class LinearWeightedTypeBasedOpenList : public OpenList { + utils::RandomNumberGenerator rng; + vector> evaluators; + + using Key = vector; + using Bucket = vector; + unordered_map>> first_to_keys_and_buckets; + unordered_map> first_to_key_to_bucket_index; + std::set first_values; + + double alpha; + double beta; + bool ignore_size; + bool ignore_weights; + +protected: + virtual void do_insertion( + EvaluationContext &eval_context, const Entry &entry) override; + +public: + explicit LinearWeightedTypeBasedOpenList(const plugins::Options &opts); + virtual ~LinearWeightedTypeBasedOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual bool is_dead_end(EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; + virtual void get_path_dependent_evaluators(set &evals) override; +}; + +template +void LinearWeightedTypeBasedOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + vector key; + key.reserve(evaluators.size() - 1); + bool first = true; + int key_first = -1; + for (const shared_ptr &evaluator : evaluators) { + if (first) { + key_first = eval_context.get_evaluator_value_or_infinity(evaluator.get()); + first = false; + } else { + key.push_back( + eval_context.get_evaluator_value_or_infinity(evaluator.get())); + } + } + + auto first_it = first_to_key_to_bucket_index.find(key_first); + if (first_it == first_to_key_to_bucket_index.end()) { + first_values.insert(key_first); + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + first_to_key_to_bucket_index[key_first][key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + } else { + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + auto it = key_to_bucket_index.find(key); + if (it == key_to_bucket_index.end()) { + key_to_bucket_index[key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + } else { + size_t bucket_index = it->second; + assert(utils::in_bounds(bucket_index, keys_and_buckets)); + keys_and_buckets[bucket_index].second.push_back(entry); + } + } +} + +template +LinearWeightedTypeBasedOpenList::LinearWeightedTypeBasedOpenList(const plugins::Options &opts) + : rng(opts.get("random_seed")), + evaluators(opts.get_list>("evaluators")), + alpha(opts.get("alpha")), + beta(opts.get("beta")), + ignore_size(opts.get("ignore_size")), + ignore_weights(opts.get("ignore_weights")) { +} + +template +Entry LinearWeightedTypeBasedOpenList::remove_min() { + int key_first = *first_values.begin(); + if (first_values.size() > 1) { + double r = rng.random(); + double bias = static_cast(*first_values.rbegin()) + beta; + + double numerator = 0.0; + for (auto value : first_values) { + double p = 1.0; + + if (!ignore_weights) + p *= -1 * alpha * static_cast(value) + bias; + + if (!ignore_size) + p *= static_cast(first_to_keys_and_buckets[value].size()); + + numerator += p; + } + + double p_sum = 0.0; + for (auto value : first_values) { + double p = 1.0 / numerator; + + if (!ignore_weights) + p *= -1 * alpha * static_cast(value) + bias; + + if (!ignore_size) + p *= static_cast(first_to_keys_and_buckets[value].size()); + + p_sum += p; + if (r <= p_sum) { + key_first = value; + break; + } + } + } + + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + + size_t bucket_id = rng.random(keys_and_buckets.size()); + auto &key_and_bucket = keys_and_buckets[bucket_id]; + const Key &min_key = key_and_bucket.first; + Bucket &bucket = key_and_bucket.second; + int pos = rng.random(bucket.size()); + Entry result = utils::swap_and_pop_from_vector(bucket, pos); + + if (bucket.empty()) { + // Swap the empty bucket with the last bucket, then delete it. + key_to_bucket_index[keys_and_buckets.back().first] = bucket_id; + key_to_bucket_index.erase(min_key); + utils::swap_and_pop_from_vector(keys_and_buckets, bucket_id); + + if (keys_and_buckets.empty()) { + first_to_keys_and_buckets.erase(key_first); + first_to_key_to_bucket_index.erase(key_first); + first_values.erase(key_first); + } + } + + return result; +} + +template +bool LinearWeightedTypeBasedOpenList::empty() { + return first_values.empty(); +} + +template +void LinearWeightedTypeBasedOpenList::clear() { + first_to_keys_and_buckets.clear(); + first_to_key_to_bucket_index.clear(); + first_to_keys_and_buckets.clear(); +} + +template +bool LinearWeightedTypeBasedOpenList::is_dead_end( + EvaluationContext &eval_context) const { + // If one evaluator is sure we have a dead end, return true. + if (is_reliable_dead_end(eval_context)) + return true; + // Otherwise, return true if all evaluators agree this is a dead-end. + for (const shared_ptr &evaluator : evaluators) { + if (!eval_context.is_evaluator_value_infinite(evaluator.get())) + return false; + } + return true; +} + +template +bool LinearWeightedTypeBasedOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable() && + eval_context.is_evaluator_value_infinite(evaluator.get())) + return true; + } + return false; +} + +template +void LinearWeightedTypeBasedOpenList::get_path_dependent_evaluators( + set &evals) { + for (const shared_ptr &evaluator : evaluators) { + evaluator->get_path_dependent_evaluators(evals); + } +} + +LinearWeightedTypeBasedOpenListFactory::LinearWeightedTypeBasedOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +LinearWeightedTypeBasedOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +LinearWeightedTypeBasedOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class LinearWeightedTypeBasedOpenListFeature : public plugins::TypedFeature { +public: + LinearWeightedTypeBasedOpenListFeature(): TypedFeature("linear_weighted_type_based"){ + document_title("LinearWeightedType-based open list"); + document_synopsis( + "Uses multiple evaluators to assign entries to buckets. " + "All entries in a bucket have the same evaluator values. " + "When retrieving an entry, a bucket is chosen uniformly at " + "random and one of the contained entries is selected " + "uniformly randomly. " + "The algorithm is based on" + utils::format_conference_reference( + {"Fan Xie", "Martin Mueller", "Robert Holte", "Tatsuya Imai"}, + "LinearWeightedType-Based Exploration with Multiple Search Queues for" + " Satisficing Planning", + "http://www.aaai.org/ocs/index.php/AAAI/AAAI14/paper/view/8472/8705", + "Proceedings of the Twenty-Eigth AAAI Conference Conference" + " on Artificial Intelligence (AAAI 2014)", + "2395-2401", + "AAAI Press", + "2014")); + add_list_option>( + "evaluators", + "Evaluators used to determine the bucket for each entry."); + add_option("alpha", "coefficient", "1.0"); + add_option("beta", "bias", "1.0"); + add_option( + "ignore_size", + "ignore size of second to last keys", "false"); + add_option( + "ignore_weights", + "ignore linear_weighted weights", "false"); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + diff --git a/src/search/open_lists/linear_weighted_type_based_open_list.h b/src/search/open_lists/linear_weighted_type_based_open_list.h new file mode 100644 index 0000000000..eb851494a3 --- /dev/null +++ b/src/search/open_lists/linear_weighted_type_based_open_list.h @@ -0,0 +1,29 @@ +#ifndef OPEN_LISTS_LINEAR_WEIGHTED_TYPE_BASED_OPEN_LIST_H +#define OPEN_LISTS_LINEAR_WEIGHTED_TYPE_BASED_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + +/* + Lin-Type(h) in Kuroiwa & Beck (ICAPS2022). + Instead of Type-based open list which selects a random h uniformly, + select h with a probability that is linearly interpolated between max/min h. + + This is an implementation for multiple evaluators. +*/ + +namespace linear_weighted_type_based_open_list { +class LinearWeightedTypeBasedOpenListFactory : public OpenListFactory { + plugins::Options options; +public: + explicit LinearWeightedTypeBasedOpenListFactory(const plugins::Options &options); + virtual ~LinearWeightedTypeBasedOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} + +#endif + + From b3c00bf898545e08cb81921d6e514b76e0d7c079 Mon Sep 17 00:00:00 2001 From: Masataro Asai Date: Wed, 15 Jan 2025 00:46:55 -0500 Subject: [PATCH 3/3] N-type(h) (Kuroiwa & Beck 2022) ported from https://github.com/Kurorororo/biased-exploration --- src/search/CMakeLists.txt | 8 + .../open_lists/nth_best_first_open_list.cc | 158 +++++++++++ .../open_lists/nth_best_first_open_list.h | 27 ++ .../open_lists/nth_type_based_open_list.cc | 257 ++++++++++++++++++ .../open_lists/nth_type_based_open_list.h | 22 ++ 5 files changed, 472 insertions(+) create mode 100644 src/search/open_lists/nth_best_first_open_list.cc create mode 100644 src/search/open_lists/nth_best_first_open_list.h create mode 100644 src/search/open_lists/nth_type_based_open_list.cc create mode 100644 src/search/open_lists/nth_type_based_open_list.h diff --git a/src/search/CMakeLists.txt b/src/search/CMakeLists.txt index 37c437ba2a..a5d9d670af 100644 --- a/src/search/CMakeLists.txt +++ b/src/search/CMakeLists.txt @@ -220,6 +220,14 @@ create_fast_downward_library( open_lists/linear_weighted_type_based_open_list ) +create_fast_downward_library( + NAME NTH_BEST_FIRST_OPEN_LIST + HELP "Open list that randomly selects from the n th best element. (Kuroiwa and Beck, ICAPS2022)" + SOURCES + open_lists/nth_best_first_open_list + open_lists/nth_type_based_open_list +) + create_fast_downward_library( NAME dynamic_bitset HELP "Poor man's version of boost::dynamic_bitset" diff --git a/src/search/open_lists/nth_best_first_open_list.cc b/src/search/open_lists/nth_best_first_open_list.cc new file mode 100644 index 0000000000..c7a0324570 --- /dev/null +++ b/src/search/open_lists/nth_best_first_open_list.cc @@ -0,0 +1,158 @@ +#include "nth_best_first_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../plugins/plugin.h" +#include "../utils/memory.h" + +#include +#include +#include + +using namespace std; + +namespace nth_best_first_open_list { +template +class NthBestFirstOpenList : public OpenList { + typedef deque Bucket; + + int n; + map buckets; + int size; + + shared_ptr evaluator; + +protected: + virtual void do_insertion(EvaluationContext &eval_context, + const Entry &entry) override; + +public: + explicit NthBestFirstOpenList(const plugins::Options &opts); + NthBestFirstOpenList(int n, const shared_ptr &eval, bool preferred_only); + virtual ~NthBestFirstOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual void get_path_dependent_evaluators(set &evals) override; + virtual bool is_dead_end( + EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; +}; + + +template +NthBestFirstOpenList::NthBestFirstOpenList(const plugins::Options &opts) + : OpenList(opts.get("pref_only")), + n(opts.get("n")), + size(0), + evaluator(opts.get>("eval")) { +} + +template +NthBestFirstOpenList::NthBestFirstOpenList( + int n, const shared_ptr &evaluator, bool preferred_only) + : OpenList(preferred_only), + n(n), + size(0), + evaluator(evaluator) { +} + +template +void NthBestFirstOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + int key = eval_context.get_evaluator_value(evaluator.get()); + buckets[key].push_back(entry); + ++size; +} + +template +Entry NthBestFirstOpenList::remove_min() { + assert(size > 0); + auto it = buckets.begin(); + assert(it != buckets.end()); + int i = 1; + while (next(it) != buckets.end() && i < n) { + ++it; + ++i; + } + Bucket &bucket = it->second; + assert(!bucket.empty()); + Entry result = bucket.front(); + bucket.pop_front(); + if (bucket.empty()) + buckets.erase(it); + --size; + return result; +} + +template +bool NthBestFirstOpenList::empty() { + return size == 0; +} + +template +void NthBestFirstOpenList::clear() { + buckets.clear(); + size = 0; +} + +template +void NthBestFirstOpenList::get_path_dependent_evaluators( + set &evals) { + evaluator->get_path_dependent_evaluators(evals); +} + +template +bool NthBestFirstOpenList::is_dead_end( + EvaluationContext &eval_context) const { + return eval_context.is_evaluator_value_infinite(evaluator.get()); +} + +template +bool NthBestFirstOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); +} + +NthBestFirstOpenListFactory::NthBestFirstOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +NthBestFirstOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +NthBestFirstOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class NthBestFirstOpenListFeature : public plugins::TypedFeature { +public: + NthBestFirstOpenListFeature(): TypedFeature("nth") { + document_title("n th best-first open list"); + document_synopsis( + "Open list that uses a single evaluator and FIFO tiebreaking and returns a node having the n th best evaluation value."); + document_note( + "Implementation Notes", + "Elements with the same evaluator value are stored in double-ended " + "queues, called \"buckets\". The open list stores a map from evaluator " + "values to buckets. Pushing and popping from a bucket runs in constant " + "time. Therefore, inserting and removing an entry from the open list " + "takes time O(log(n)), where n is the number of buckets."); + add_option>("eval", "evaluator"); + add_option( + "pref_only", + "insert only nodes generated by preferred operators", "false"); + add_option("n", "n", "2"); + } +}; + +static plugins::FeaturePlugin _plugin; +} + diff --git a/src/search/open_lists/nth_best_first_open_list.h b/src/search/open_lists/nth_best_first_open_list.h new file mode 100644 index 0000000000..b4dbba0a1d --- /dev/null +++ b/src/search/open_lists/nth_best_first_open_list.h @@ -0,0 +1,27 @@ +#ifndef OPEN_LISTS_NTH_BEST_FIRST_OPEN_LIST_H +#define OPEN_LISTS_NTH_BEST_FIRST_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + + +/* + Open list indexed by a single int, using FIFO tie-breaking. + + Implemented as a map from int to deques. +*/ + +namespace nth_best_first_open_list { +class NthBestFirstOpenListFactory : public OpenListFactory { + plugins::Options options; +public: + explicit NthBestFirstOpenListFactory(const plugins::Options &options); + virtual ~NthBestFirstOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} + +#endif + diff --git a/src/search/open_lists/nth_type_based_open_list.cc b/src/search/open_lists/nth_type_based_open_list.cc new file mode 100644 index 0000000000..adb9f885f3 --- /dev/null +++ b/src/search/open_lists/nth_type_based_open_list.cc @@ -0,0 +1,257 @@ +#include "nth_type_based_open_list.h" + +#include "../evaluator.h" +#include "../open_list.h" + +#include "../utils/collections.h" +#include "../utils/hash.h" +#include "../utils/markup.h" +#include "../plugins/plugin.h" +#include "../utils/memory.h" +#include "../utils/rng.h" +#include "../utils/rng_options.h" + +#include +#include +#include +#include + +using namespace std; + +namespace nth_type_based_open_list { +template +class NthTypeBasedOpenList : public OpenList { + utils::RandomNumberGenerator rng; + vector> evaluators; + + using Key = vector; + using Bucket = vector; + unordered_map>> first_to_keys_and_buckets; + unordered_map> first_to_key_to_bucket_index; + std::set first_values; + + int n; + bool ignore_size; + +protected: + virtual void do_insertion( + EvaluationContext &eval_context, const Entry &entry) override; + +public: + explicit NthTypeBasedOpenList(const plugins::Options &opts); + virtual ~NthTypeBasedOpenList() override = default; + + virtual Entry remove_min() override; + virtual bool empty() override; + virtual void clear() override; + virtual bool is_dead_end(EvaluationContext &eval_context) const override; + virtual bool is_reliable_dead_end( + EvaluationContext &eval_context) const override; + virtual void get_path_dependent_evaluators(set &evals) override; +}; + +template +void NthTypeBasedOpenList::do_insertion( + EvaluationContext &eval_context, const Entry &entry) { + vector key; + key.reserve(evaluators.size() - 1); + bool first = true; + int key_first = -1; + for (const shared_ptr &evaluator : evaluators) { + if (first) { + key_first = eval_context.get_evaluator_value_or_infinity(evaluator.get()); + first = false; + } else { + key.push_back( + eval_context.get_evaluator_value_or_infinity(evaluator.get())); + } + } + + auto first_it = first_to_key_to_bucket_index.find(key_first); + if (first_it == first_to_key_to_bucket_index.end()) { + first_values.insert(key_first); + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + first_to_key_to_bucket_index[key_first][key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + } else { + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + auto it = key_to_bucket_index.find(key); + if (it == key_to_bucket_index.end()) { + key_to_bucket_index[key] = keys_and_buckets.size(); + keys_and_buckets.push_back(make_pair(move(key), Bucket({entry}))); + } else { + size_t bucket_index = it->second; + assert(utils::in_bounds(bucket_index, keys_and_buckets)); + keys_and_buckets[bucket_index].second.push_back(entry); + } + } +} + +template +NthTypeBasedOpenList::NthTypeBasedOpenList(const plugins::Options &opts) + : rng(opts.get("random_seed")), + evaluators(opts.get_list>("evaluators")), + n(opts.get("n")), + ignore_size(opts.get("ignore_size")) { +} + +template +Entry NthTypeBasedOpenList::remove_min() { + int key_first = *first_values.begin(); + if (first_values.size() > 1) { + double current_sum = 0.0; + + if (ignore_size) { + current_sum = n; + } else { + int i = 0; + for (auto value : first_values) { + if (ignore_size) + current_sum += 1.0; + else + current_sum += static_cast(first_to_keys_and_buckets[value].size()); + if (i++ >= n) break; + } + } + + double r = rng.random(); + double p_sum = 0.0; + int i = 0; + for (auto value : first_values) { + double p = 1.0 / current_sum; + + if (!ignore_size) + p *= static_cast(first_to_keys_and_buckets[value].size()); + + p_sum += p; + if (r <= p_sum) { + key_first = value; + break; + } + if (i++ >= n) break; + } + } + + auto &keys_and_buckets = first_to_keys_and_buckets[key_first]; + auto &key_to_bucket_index = first_to_key_to_bucket_index[key_first]; + + size_t bucket_id = rng.random(keys_and_buckets.size()); + auto &key_and_bucket = keys_and_buckets[bucket_id]; + const Key &min_key = key_and_bucket.first; + Bucket &bucket = key_and_bucket.second; + int pos = rng.random(bucket.size()); + Entry result = utils::swap_and_pop_from_vector(bucket, pos); + + if (bucket.empty()) { + // Swap the empty bucket with the last bucket, then delete it. + key_to_bucket_index[keys_and_buckets.back().first] = bucket_id; + key_to_bucket_index.erase(min_key); + utils::swap_and_pop_from_vector(keys_and_buckets, bucket_id); + + if (keys_and_buckets.empty()) { + first_to_keys_and_buckets.erase(key_first); + first_to_key_to_bucket_index.erase(key_first); + first_values.erase(key_first); + } + } + + return result; +} + +template +bool NthTypeBasedOpenList::empty() { + return first_values.empty(); +} + +template +void NthTypeBasedOpenList::clear() { + first_to_keys_and_buckets.clear(); + first_to_key_to_bucket_index.clear(); + first_to_keys_and_buckets.clear(); +} + +template +bool NthTypeBasedOpenList::is_dead_end( + EvaluationContext &eval_context) const { + // If one evaluator is sure we have a dead end, return true. + if (is_reliable_dead_end(eval_context)) + return true; + // Otherwise, return true if all evaluators agree this is a dead-end. + for (const shared_ptr &evaluator : evaluators) { + if (!eval_context.is_evaluator_value_infinite(evaluator.get())) + return false; + } + return true; +} + +template +bool NthTypeBasedOpenList::is_reliable_dead_end( + EvaluationContext &eval_context) const { + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable() && + eval_context.is_evaluator_value_infinite(evaluator.get())) + return true; + } + return false; +} + +template +void NthTypeBasedOpenList::get_path_dependent_evaluators( + set &evals) { + for (const shared_ptr &evaluator : evaluators) { + evaluator->get_path_dependent_evaluators(evals); + } +} + +NthTypeBasedOpenListFactory::NthTypeBasedOpenListFactory( + const plugins::Options &options) + : options(options) { +} + +unique_ptr +NthTypeBasedOpenListFactory::create_state_open_list() { + return utils::make_unique_ptr>(options); +} + +unique_ptr +NthTypeBasedOpenListFactory::create_edge_open_list() { + return utils::make_unique_ptr>(options); +} + +class NthTypeBasedOpenListFeature : public plugins::TypedFeature { +public: + NthTypeBasedOpenListFeature(): TypedFeature("nth_type_based"){ + document_title("NthType-based open list"); + document_synopsis( + "Uses multiple evaluators to assign entries to buckets. " + "All entries in a bucket have the same evaluator values. " + "When retrieving an entry, a bucket is chosen uniformly at " + "random and one of the contained entries is selected " + "uniformly randomly. " + "The algorithm is based on" + utils::format_conference_reference( + {"Fan Xie", "Martin Mueller", "Robert Holte", "Tatsuya Imai"}, + "NthType-Based Exploration with Multiple Search Queues for" + " Satisficing Planning", + "http://www.aaai.org/ocs/index.php/AAAI/AAAI14/paper/view/8472/8705", + "Proceedings of the Twenty-Eigth AAAI Conference Conference" + " on Artificial Intelligence (AAAI 2014)", + "2395-2401", + "AAAI Press", + "2014")); + add_list_option>( + "evaluators", + "Evaluators used to determine the bucket for each entry."); + add_option("n", "how many h-values to explore", "2"); + add_option( + "ignore_size", + "ignore size of second to last keys", "false"); + + utils::add_rng_options(*this); + } +}; + +static plugins::FeaturePlugin _plugin; +} + + diff --git a/src/search/open_lists/nth_type_based_open_list.h b/src/search/open_lists/nth_type_based_open_list.h new file mode 100644 index 0000000000..14412e4ffd --- /dev/null +++ b/src/search/open_lists/nth_type_based_open_list.h @@ -0,0 +1,22 @@ +#ifndef OPEN_LISTS_NTH_TYPE_BASED_OPEN_LIST_H +#define OPEN_LISTS_NTH_TYPE_BASED_OPEN_LIST_H + +#include "../open_list_factory.h" +#include "../plugins/options.h" + + +namespace nth_type_based_open_list { +class NthTypeBasedOpenListFactory : public OpenListFactory { + plugins::Options options; +public: + explicit NthTypeBasedOpenListFactory(const plugins::Options &options); + virtual ~NthTypeBasedOpenListFactory() override = default; + + virtual std::unique_ptr create_state_open_list() override; + virtual std::unique_ptr create_edge_open_list() override; +}; +} + +#endif + +