From b3c00bf898545e08cb81921d6e514b76e0d7c079 Mon Sep 17 00:00:00 2001 From: Masataro Asai Date: Wed, 15 Jan 2025 00:46:55 -0500 Subject: [PATCH] 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 + +