From 95078e921d6cebaafd8bf724944f426cdbc79deb Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Tue, 10 Feb 2026 17:37:24 +0100 Subject: [PATCH 01/29] Add completeness check to eager search, all pruning methods, and some open lists. --- src/search/open_list.h | 3 +++ .../open_lists/alternation_open_list.cc | 11 ++++++++ src/search/open_lists/best_first_open_list.cc | 9 +++++++ .../open_lists/epsilon_greedy_open_list.cc | 9 +++++++ .../open_lists/tiebreaking_open_list.cc | 25 +++++++++++++++++++ src/search/planner.cc | 11 +++++--- src/search/pruning/limited_pruning.cc | 4 +++ src/search/pruning/limited_pruning.h | 1 + src/search/pruning/null_pruning_method.h | 3 +++ src/search/pruning/stubborn_sets.h | 3 +++ src/search/pruning_method.h | 1 + src/search/search_algorithm.h | 3 +++ src/search/search_algorithms/eager_search.cc | 10 ++++++++ src/search/search_algorithms/eager_search.h | 1 + 14 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/search/open_list.h b/src/search/open_list.h index dbedc7ba05..2caf54c2a4 100644 --- a/src/search/open_list.h +++ b/src/search/open_list.h @@ -122,6 +122,9 @@ class OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const = 0; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const = 0; + virtual bool pruning_is_safe() const { + return false; + } }; using StateOpenListEntry = StateID; diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index 0067bd8f0a..5b7af75bd6 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -37,6 +37,7 @@ class AlternationOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; + virtual bool pruning_is_safe() const override; }; template @@ -124,6 +125,16 @@ bool AlternationOpenList::is_reliable_dead_end( return false; } +template +bool AlternationOpenList::pruning_is_safe() const { + for (const auto &sublist: open_lists) { + if (sublist->pruning_is_safe()) { + return true; + } + } + return false; +} + AlternationOpenListFactory::AlternationOpenListFactory( const vector> &sublists, int boost) : sublists(sublists), boost(boost) { diff --git a/src/search/open_lists/best_first_open_list.cc b/src/search/open_lists/best_first_open_list.cc index ad682f2f77..4e8a3c2110 100644 --- a/src/search/open_lists/best_first_open_list.cc +++ b/src/search/open_lists/best_first_open_list.cc @@ -36,6 +36,7 @@ class BestFirstOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; + virtual bool pruning_is_safe() const override; }; template @@ -96,6 +97,14 @@ bool BestFirstOpenList::is_reliable_dead_end( return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); } +template +bool BestFirstOpenList::pruning_is_safe() const { + if (this->only_contains_preferred_entries()) { + return false; + } + return evaluator->dead_ends_are_reliable(); +} + BestFirstOpenListFactory::BestFirstOpenListFactory( const shared_ptr &eval, bool pref_only) : eval(eval), pref_only(pref_only) { diff --git a/src/search/open_lists/epsilon_greedy_open_list.cc b/src/search/open_lists/epsilon_greedy_open_list.cc index 9774f8b4cd..f1a2b6c313 100644 --- a/src/search/open_lists/epsilon_greedy_open_list.cc +++ b/src/search/open_lists/epsilon_greedy_open_list.cc @@ -52,6 +52,7 @@ class EpsilonGreedyOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; + virtual bool pruning_is_safe() const override; virtual void get_path_dependent_evaluators( set &evals) override; virtual bool empty() const override; @@ -119,6 +120,14 @@ bool EpsilonGreedyOpenList::is_reliable_dead_end( return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); } +template +bool EpsilonGreedyOpenList::pruning_is_safe() const { + if (this->only_contains_preferred_entries()) { + return false; + } + return evaluator->dead_ends_are_reliable(); +} + template void EpsilonGreedyOpenList::get_path_dependent_evaluators( set &evals) { diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index 8a50ad6e27..75f50b7be0 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -48,6 +48,7 @@ class TieBreakingOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; + virtual bool pruning_is_safe() const override; }; template @@ -140,6 +141,30 @@ bool TieBreakingOpenList::is_reliable_dead_end( return false; } +template +bool TieBreakingOpenList::pruning_is_safe() const { + if (this->only_contains_preferred_entries()) { + return false; + } + assert(!evaluators.empty()); + if (evaluators[0]->dead_ends_are_reliable()) { + return true; + } + // At this point we know that the first evaluator is unsafe. + if (allow_unsafe_pruning) { + return false; + } + // Even if the first evaluator is unsafe we can still say that + // pruning_is_safe is true if (allow_unsafe_pruning is false and) at least + // one other evaluator is safe. + for (auto eval: evaluators) { + if (eval->dead_ends_are_reliable()) { + return true; + } + } + return false; +} + TieBreakingOpenListFactory::TieBreakingOpenListFactory( const vector> &evals, bool unsafe_pruning, bool pref_only) diff --git a/src/search/planner.cc b/src/search/planner.cc index 2f5addf422..fbfde2d239 100644 --- a/src/search/planner.cc +++ b/src/search/planner.cc @@ -53,9 +53,14 @@ int main(int argc, const char **argv) { utils::g_log << "Search time: " << search_timer << endl; utils::g_log << "Total time: " << utils::g_timer << endl; - ExitCode exitcode = search_algorithm->found_solution() - ? ExitCode::SUCCESS - : ExitCode::SEARCH_UNSOLVED_INCOMPLETE; + ExitCode exitcode; + if (search_algorithm->found_solution()) { + exitcode = ExitCode::SUCCESS; + } else if (search_algorithm->is_complete()) { + exitcode = ExitCode::SEARCH_UNSOLVABLE; + } else { + exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; + } exit_with(exitcode); } catch (const utils::ExitException &e) { /* To ensure that all destructors are called before the program exits, diff --git a/src/search/pruning/limited_pruning.cc b/src/search/pruning/limited_pruning.cc index 1124046464..86b78f824d 100644 --- a/src/search/pruning/limited_pruning.cc +++ b/src/search/pruning/limited_pruning.cc @@ -24,6 +24,10 @@ void LimitedPruning::initialize(const shared_ptr &task) { log << "pruning method: limited" << endl; } +bool LimitedPruning::is_safe() const { + return pruning_method->is_safe(); +} + void LimitedPruning::prune(const State &state, vector &op_ids) { if (is_pruning_disabled) { return; diff --git a/src/search/pruning/limited_pruning.h b/src/search/pruning/limited_pruning.h index 11b4c89d7f..1b41f3a445 100644 --- a/src/search/pruning/limited_pruning.h +++ b/src/search/pruning/limited_pruning.h @@ -24,6 +24,7 @@ class LimitedPruning : public PruningMethod { int expansions_before_checking_pruning_ratio, utils::Verbosity verbosity); virtual void initialize(const std::shared_ptr &) override; + virtual bool is_safe() const override; }; } diff --git a/src/search/pruning/null_pruning_method.h b/src/search/pruning/null_pruning_method.h index 934f167ba8..29d986ee49 100644 --- a/src/search/pruning/null_pruning_method.h +++ b/src/search/pruning/null_pruning_method.h @@ -12,6 +12,9 @@ class NullPruningMethod : public PruningMethod { virtual void initialize(const std::shared_ptr &) override; virtual void print_statistics() const override { } + virtual bool is_safe() const override { + return true; + } }; } diff --git a/src/search/pruning/stubborn_sets.h b/src/search/pruning/stubborn_sets.h index c3f1a8f923..51157cba70 100644 --- a/src/search/pruning/stubborn_sets.h +++ b/src/search/pruning/stubborn_sets.h @@ -58,6 +58,9 @@ class StubbornSets : public PruningMethod { public: explicit StubbornSets(utils::Verbosity verbosity); virtual void initialize(const std::shared_ptr &task) override; + virtual bool is_safe() const override { + return true; + } }; // Return the first unsatified condition, or FactPair::no_fact if there is none. diff --git a/src/search/pruning_method.h b/src/search/pruning_method.h index 861ed3affb..63e48d6224 100644 --- a/src/search/pruning_method.h +++ b/src/search/pruning_method.h @@ -37,6 +37,7 @@ class PruningMethod { virtual void initialize(const std::shared_ptr &task); void prune_operators(const State &state, std::vector &op_ids); virtual void print_statistics() const; + virtual bool is_safe() const = 0; }; extern void add_pruning_options_to_feature(plugins::Feature &feature); diff --git a/src/search/search_algorithm.h b/src/search/search_algorithm.h index 9ed2d571c3..630f740c85 100644 --- a/src/search/search_algorithm.h +++ b/src/search/search_algorithm.h @@ -77,6 +77,9 @@ class SearchAlgorithm { virtual ~SearchAlgorithm(); virtual void print_statistics() const = 0; virtual void save_plan_if_necessary(); + virtual bool is_complete() const { + return false; + } bool found_solution() const; SearchStatus get_status() const; const Plan &get_plan() const; diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index 99be531179..8a9dcbbe50 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -318,6 +318,16 @@ void EagerSearch::dump_search_space() const { search_space.dump(task_proxy); } +bool EagerSearch::is_complete() const { + if (!open_list->pruning_is_safe()) { + return false; + } + if (!pruning_method->is_safe()) { + return false; + } + return true; +} + void EagerSearch::start_f_value_statistics(EvaluationContext &eval_context) { if (f_evaluator) { int f_value = eval_context.get_evaluator_value(f_evaluator.get()); diff --git a/src/search/search_algorithms/eager_search.h b/src/search/search_algorithms/eager_search.h index 9d3da156ad..adb300cccc 100644 --- a/src/search/search_algorithms/eager_search.h +++ b/src/search/search_algorithms/eager_search.h @@ -57,6 +57,7 @@ class EagerSearch : public SearchAlgorithm { virtual void print_statistics() const override; void dump_search_space() const; + virtual bool is_complete() const override; }; extern void add_eager_search_options_to_feature( From fa784a2a3f9cd4f90d20bd09de8e79b502a04a9f Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Wed, 11 Feb 2026 11:53:06 +0100 Subject: [PATCH 02/29] Add comments and shorten EagerSearch::is_complete. --- src/search/open_lists/alternation_open_list.cc | 2 ++ src/search/open_lists/tiebreaking_open_list.cc | 2 +- src/search/search_algorithms/eager_search.cc | 8 +------- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index 5b7af75bd6..bbb450b8c9 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -127,6 +127,8 @@ bool AlternationOpenList::is_reliable_dead_end( template bool AlternationOpenList::pruning_is_safe() const { + // If at least one of the sub-lists ensures that no solvable state is + // pruned we know that this also holds for AlternationOpenList. for (const auto &sublist: open_lists) { if (sublist->pruning_is_safe()) { return true; diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index 75f50b7be0..e92ade8678 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -155,7 +155,7 @@ bool TieBreakingOpenList::pruning_is_safe() const { return false; } // Even if the first evaluator is unsafe we can still say that - // pruning_is_safe is true if (allow_unsafe_pruning is false and) at least + // pruning is safe if (allow_unsafe_pruning is false and) at least // one other evaluator is safe. for (auto eval: evaluators) { if (eval->dead_ends_are_reliable()) { diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index 8a9dcbbe50..60dd3a6b87 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -319,13 +319,7 @@ void EagerSearch::dump_search_space() const { } bool EagerSearch::is_complete() const { - if (!open_list->pruning_is_safe()) { - return false; - } - if (!pruning_method->is_safe()) { - return false; - } - return true; + return open_list->pruning_is_safe() && pruning_method->is_safe(); } void EagerSearch::start_f_value_statistics(EvaluationContext &eval_context) { From 25939f49b2b095ae94f47819ef510152d74e0600 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Wed, 11 Feb 2026 16:52:27 +0100 Subject: [PATCH 03/29] Add completeness check to lazy search. --- src/search/open_list.h | 2 +- src/search/open_lists/alternation_open_list.cc | 2 +- src/search/open_lists/tiebreaking_open_list.cc | 2 +- src/search/search_algorithms/lazy_search.cc | 4 ++++ src/search/search_algorithms/lazy_search.h | 1 + 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/search/open_list.h b/src/search/open_list.h index 2caf54c2a4..5487418eaf 100644 --- a/src/search/open_list.h +++ b/src/search/open_list.h @@ -123,7 +123,7 @@ class OpenList { virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const = 0; virtual bool pruning_is_safe() const { - return false; + return false; } }; diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index bbb450b8c9..30d2950e14 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -129,7 +129,7 @@ template bool AlternationOpenList::pruning_is_safe() const { // If at least one of the sub-lists ensures that no solvable state is // pruned we know that this also holds for AlternationOpenList. - for (const auto &sublist: open_lists) { + for (const auto &sublist : open_lists) { if (sublist->pruning_is_safe()) { return true; } diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index e92ade8678..52aeb7b71a 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -157,7 +157,7 @@ bool TieBreakingOpenList::pruning_is_safe() const { // Even if the first evaluator is unsafe we can still say that // pruning is safe if (allow_unsafe_pruning is false and) at least // one other evaluator is safe. - for (auto eval: evaluators) { + for (auto eval : evaluators) { if (eval->dead_ends_are_reliable()) { return true; } diff --git a/src/search/search_algorithms/lazy_search.cc b/src/search/search_algorithms/lazy_search.cc index dd5afac207..194bdcc99f 100644 --- a/src/search/search_algorithms/lazy_search.cc +++ b/src/search/search_algorithms/lazy_search.cc @@ -234,4 +234,8 @@ void LazySearch::print_statistics() const { statistics.print_detailed_statistics(); search_space.print_statistics(); } + +bool LazySearch::is_complete() const { + return open_list->pruning_is_safe(); +} } diff --git a/src/search/search_algorithms/lazy_search.h b/src/search/search_algorithms/lazy_search.h index ae0dbcba04..206f70e37c 100644 --- a/src/search/search_algorithms/lazy_search.h +++ b/src/search/search_algorithms/lazy_search.h @@ -58,6 +58,7 @@ class LazySearch : public SearchAlgorithm { const std::string &description, utils::Verbosity verbosity); virtual void print_statistics() const override; + virtual bool is_complete() const override; }; } From 5ea8eb25fdb1aecac5799c8943e9b4e447d2f92a Mon Sep 17 00:00:00 2001 From: Claudia Grundke <43370468+grucla@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:01:13 +0100 Subject: [PATCH 04/29] Make for loop in tiebreaking_open_list.cc consistent. Co-authored-by: SimonDold <48084373+SimonDold@users.noreply.github.com> --- src/search/open_lists/tiebreaking_open_list.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index 52aeb7b71a..a831311beb 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -157,7 +157,7 @@ bool TieBreakingOpenList::pruning_is_safe() const { // Even if the first evaluator is unsafe we can still say that // pruning is safe if (allow_unsafe_pruning is false and) at least // one other evaluator is safe. - for (auto eval : evaluators) { + for (const shared_ptr &evaluator : evaluators) if (eval->dead_ends_are_reliable()) { return true; } From efc35c9816aeea79f93619c6ce3f85047c8879c2 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 09:15:08 +0100 Subject: [PATCH 05/29] Fix error and make comments style conform. --- src/search/open_lists/alternation_open_list.cc | 4 ++-- src/search/open_lists/tiebreaking_open_list.cc | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index 30d2950e14..c7e72bda87 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -127,8 +127,8 @@ bool AlternationOpenList::is_reliable_dead_end( template bool AlternationOpenList::pruning_is_safe() const { - // If at least one of the sub-lists ensures that no solvable state is - // pruned we know that this also holds for AlternationOpenList. + /* If at least one of the sub-lists ensures that no solvable state is + pruned we know that this also holds for AlternationOpenList. */ for (const auto &sublist : open_lists) { if (sublist->pruning_is_safe()) { return true; diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index a831311beb..b71bca7444 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -154,11 +154,13 @@ bool TieBreakingOpenList::pruning_is_safe() const { if (allow_unsafe_pruning) { return false; } - // Even if the first evaluator is unsafe we can still say that - // pruning is safe if (allow_unsafe_pruning is false and) at least - // one other evaluator is safe. - for (const shared_ptr &evaluator : evaluators) - if (eval->dead_ends_are_reliable()) { + /* + Even if the first evaluator is unsafe we can still say that + pruning is safe if (allow_unsafe_pruning is false and) at least + one other evaluator is safe. + */ + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable()) { return true; } } From 3d26d49487a3c6d7f68450affd91b0adc01ae835 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 10:01:13 +0100 Subject: [PATCH 06/29] Add completeness check to pareto open list. --- src/search/open_lists/pareto_open_list.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/search/open_lists/pareto_open_list.cc b/src/search/open_lists/pareto_open_list.cc index 1018489389..3b210dfe52 100644 --- a/src/search/open_lists/pareto_open_list.cc +++ b/src/search/open_lists/pareto_open_list.cc @@ -54,6 +54,7 @@ class ParetoOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; + virtual bool pruning_is_safe() const override; }; template @@ -220,6 +221,21 @@ bool ParetoOpenList::is_reliable_dead_end( return false; } +template +bool ParetoOpenList::pruning_is_safe() const { + if (this->only_contains_preferred_entries()) { + return false; + } + /* If at least one of the evaluators ensures that no solvable state + is pruned we know that this also holds for ParetoOpenList. */ + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable()) { + return true; + } + } + return false; +} + ParetoOpenListFactory::ParetoOpenListFactory( const vector> &evals, bool state_uniform_selection, int random_seed, bool pref_only) From 94d835d716ad4b65bede5915b9409f050c15ab19 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 10:10:22 +0100 Subject: [PATCH 07/29] Add completeness check to type based open list. --- src/search/open_lists/type_based_open_list.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/search/open_lists/type_based_open_list.cc b/src/search/open_lists/type_based_open_list.cc index 9b93d98013..e22fcb4b5b 100644 --- a/src/search/open_lists/type_based_open_list.cc +++ b/src/search/open_lists/type_based_open_list.cc @@ -43,6 +43,7 @@ class TypeBasedOpenList : public OpenList { EvaluationContext &eval_context) const override; virtual void get_path_dependent_evaluators( set &evals) override; + virtual bool pruning_is_safe() const override; }; template @@ -134,6 +135,18 @@ void TypeBasedOpenList::get_path_dependent_evaluators( } } +template +bool TypeBasedOpenList::pruning_is_safe() const { + /* If at least one of the evaluators ensures that no solvable state + is pruned we know that this also holds for TypeBasedOpenList. */ + for (const shared_ptr &evaluator : evaluators) { + if (evaluator->dead_ends_are_reliable()) { + return true; + } + } + return false; +} + TypeBasedOpenListFactory::TypeBasedOpenListFactory( const vector> &evaluators, int random_seed) : evaluators(evaluators), random_seed(random_seed) { From 549ac5e8c6d7154571739330349953dcb5df6190 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 10:12:28 +0100 Subject: [PATCH 08/29] Remove default from check in open_list.h. --- src/search/open_list.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search/open_list.h b/src/search/open_list.h index 5487418eaf..a558c29f70 100644 --- a/src/search/open_list.h +++ b/src/search/open_list.h @@ -122,9 +122,7 @@ class OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const = 0; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const = 0; - virtual bool pruning_is_safe() const { - return false; - } + virtual bool pruning_is_safe() const = 0; }; using StateOpenListEntry = StateID; From 014e993cecc0fc3dbc2d1433336979c5d2419c38 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 15:46:56 +0100 Subject: [PATCH 09/29] Add completeness check to ehc. --- .../search_algorithms/enforced_hill_climbing_search.cc | 5 +++++ src/search/search_algorithms/enforced_hill_climbing_search.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.cc b/src/search/search_algorithms/enforced_hill_climbing_search.cc index ce50688c16..b1d0ef7495 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.cc +++ b/src/search/search_algorithms/enforced_hill_climbing_search.cc @@ -259,6 +259,11 @@ void EnforcedHillClimbingSearch::print_statistics() const { } } +bool EnforcedHillClimbingSearch::is_complete() const { + // Enforced hill climbing searches cannot guarantee completeness. + return false; +} + class EnforcedHillClimbingSearchFeature : public plugins::TypedFeature< SearchAlgorithm, EnforcedHillClimbingSearch> { diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.h b/src/search/search_algorithms/enforced_hill_climbing_search.h index afc49e5ce2..79095b04f8 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.h +++ b/src/search/search_algorithms/enforced_hill_climbing_search.h @@ -64,6 +64,7 @@ class EnforcedHillClimbingSearch : public SearchAlgorithm { const std::string &description, utils::Verbosity verbosity); virtual void print_statistics() const override; + virtual bool is_complete() const override; }; } From a257b08d777617b693e02cbfb8d2fe8b6468a16c Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 15:55:52 +0100 Subject: [PATCH 10/29] Add timeout check. --- src/search/planner.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search/planner.cc b/src/search/planner.cc index fbfde2d239..a609daf508 100644 --- a/src/search/planner.cc +++ b/src/search/planner.cc @@ -56,6 +56,8 @@ int main(int argc, const char **argv) { ExitCode exitcode; if (search_algorithm->found_solution()) { exitcode = ExitCode::SUCCESS; + } else if (search_algorithm->get_status() == SearchStatus::TIMEOUT) { + exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; } else if (search_algorithm->is_complete()) { exitcode = ExitCode::SEARCH_UNSOLVABLE; } else { From bc57cac9ebbc33b97e1c92e29d86cc1f877381f0 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 16:16:04 +0100 Subject: [PATCH 11/29] Add completeness check to iterated search. --- .../search_algorithms/iterated_search.cc | 26 +++++++++++++++++++ .../search_algorithms/iterated_search.h | 1 + 2 files changed, 27 insertions(+) diff --git a/src/search/search_algorithms/iterated_search.cc b/src/search/search_algorithms/iterated_search.cc index d6a0d39fa8..ef72dc8921 100644 --- a/src/search/search_algorithms/iterated_search.cc +++ b/src/search/search_algorithms/iterated_search.cc @@ -131,6 +131,32 @@ void IteratedSearch::save_plan_if_necessary() { // each successful search iteration. } +bool IteratedSearch::is_complete() const { + /* + TODO + - return true if the first search algorithm is complete + - otherwise, return false if continue_on_fail == false (and the first + search algorithm is not complete) + - otherwise (continue_on_fail == true and first search algorithm is not + complete) return true if at least one (other) search algorithm is + complete + - otherwise (continue_on_fail == true and all search algorithms are not + complete) return false + + Before we solve the component interaction problem, we cannot + access the necessary information from within a constant function. + + Once we can implement this function we also want to change IteratedSearch + such it automatically stops once a complete search finds no solution. + In that case the continue_on_fail option would not really be useful + anymore and should be removed. (Result of discussion between Claudia, + Malte, and Gabi.) + */ + log << "Warning: the completeness check for IteratedSearch is not yet implemented." + << endl; + return false; +} + class IteratedSearchFeature : public plugins::TypedFeature { public: diff --git a/src/search/search_algorithms/iterated_search.h b/src/search/search_algorithms/iterated_search.h index 8a49f0ba38..c5de1775df 100644 --- a/src/search/search_algorithms/iterated_search.h +++ b/src/search/search_algorithms/iterated_search.h @@ -36,6 +36,7 @@ class IteratedSearch : public SearchAlgorithm { virtual void save_plan_if_necessary() override; virtual void print_statistics() const override; + virtual bool is_complete() const override; }; } From a3a1ab7819b168c3c46723458bf3dd39099a5d95 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 16:18:40 +0100 Subject: [PATCH 12/29] Remove default of SearchAlgorithm::is_complete. --- src/search/search_algorithm.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search/search_algorithm.h b/src/search/search_algorithm.h index 630f740c85..b6892c1f94 100644 --- a/src/search/search_algorithm.h +++ b/src/search/search_algorithm.h @@ -77,9 +77,7 @@ class SearchAlgorithm { virtual ~SearchAlgorithm(); virtual void print_statistics() const = 0; virtual void save_plan_if_necessary(); - virtual bool is_complete() const { - return false; - } + virtual bool is_complete() const = 0; bool found_solution() const; SearchStatus get_status() const; const Plan &get_plan() const; From 48c1bfa4145b43ca80f0c295cf1d49841c70b226 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 16:35:53 +0100 Subject: [PATCH 13/29] Adjust documentation of exit code 11. --- docs/exit-codes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exit-codes.md b/docs/exit-codes.md index e587d37b3b..ba612ca881 100644 --- a/docs/exit-codes.md +++ b/docs/exit-codes.md @@ -24,8 +24,8 @@ termination which prevents the execution of further components. | **Code** | **Name** | **Meaning** | | -------- | -------- | ----------- | -| 10 | TRANSLATE_UNSOLVABLE | Translator proved task to be unsolvable. Currently not used | -| 11 | SEARCH_UNSOLVABLE | Task is provably unsolvable with current bound. Currently only used by hillclimbing search. See also [issue377](http://issues.fast-downward.org/issue377). | +| 10 | TRANSLATE_UNSOLVABLE | Translator proved task to be unsolvable. Currently not used. | +| 11 | SEARCH_UNSOLVABLE | Task is provably unsolvable with current bound. | | 12 | SEARCH_UNSOLVED_INCOMPLETE | Search ended without finding a solution. | The third block (20-29) represents expected failures which prevent the From 7a208d16b67e99a01f775a463c6c3776441c3dd3 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 17:36:45 +0100 Subject: [PATCH 14/29] Rename pruning_is_safe to is_complete. --- src/search/open_list.h | 7 ++++++- src/search/open_lists/alternation_open_list.cc | 6 +++--- src/search/open_lists/best_first_open_list.cc | 4 ++-- src/search/open_lists/epsilon_greedy_open_list.cc | 4 ++-- src/search/open_lists/pareto_open_list.cc | 4 ++-- src/search/open_lists/tiebreaking_open_list.cc | 4 ++-- src/search/open_lists/type_based_open_list.cc | 4 ++-- src/search/search_algorithms/eager_search.cc | 2 +- src/search/search_algorithms/lazy_search.cc | 2 +- 9 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/search/open_list.h b/src/search/open_list.h index a558c29f70..23b76b32df 100644 --- a/src/search/open_list.h +++ b/src/search/open_list.h @@ -122,7 +122,12 @@ class OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const = 0; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const = 0; - virtual bool pruning_is_safe() const = 0; + /* + is_complete returns true if the open list cannot "overlook" any solvable + state. Thus, if the open list is "complete" then it can be used to detect + unsolvability of a task. + */ + virtual bool is_complete() const = 0; }; using StateOpenListEntry = StateID; diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index c7e72bda87..85cf49ec50 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -37,7 +37,7 @@ class AlternationOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; }; template @@ -126,11 +126,11 @@ bool AlternationOpenList::is_reliable_dead_end( } template -bool AlternationOpenList::pruning_is_safe() const { +bool AlternationOpenList::is_complete() const { /* If at least one of the sub-lists ensures that no solvable state is pruned we know that this also holds for AlternationOpenList. */ for (const auto &sublist : open_lists) { - if (sublist->pruning_is_safe()) { + if (sublist->is_complete()) { return true; } } diff --git a/src/search/open_lists/best_first_open_list.cc b/src/search/open_lists/best_first_open_list.cc index 4e8a3c2110..42887b915d 100644 --- a/src/search/open_lists/best_first_open_list.cc +++ b/src/search/open_lists/best_first_open_list.cc @@ -36,7 +36,7 @@ class BestFirstOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; }; template @@ -98,7 +98,7 @@ bool BestFirstOpenList::is_reliable_dead_end( } template -bool BestFirstOpenList::pruning_is_safe() const { +bool BestFirstOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } diff --git a/src/search/open_lists/epsilon_greedy_open_list.cc b/src/search/open_lists/epsilon_greedy_open_list.cc index f1a2b6c313..072ec74fc8 100644 --- a/src/search/open_lists/epsilon_greedy_open_list.cc +++ b/src/search/open_lists/epsilon_greedy_open_list.cc @@ -52,7 +52,7 @@ class EpsilonGreedyOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; virtual void get_path_dependent_evaluators( set &evals) override; virtual bool empty() const override; @@ -121,7 +121,7 @@ bool EpsilonGreedyOpenList::is_reliable_dead_end( } template -bool EpsilonGreedyOpenList::pruning_is_safe() const { +bool EpsilonGreedyOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } diff --git a/src/search/open_lists/pareto_open_list.cc b/src/search/open_lists/pareto_open_list.cc index 3b210dfe52..f9b4bd8e11 100644 --- a/src/search/open_lists/pareto_open_list.cc +++ b/src/search/open_lists/pareto_open_list.cc @@ -54,7 +54,7 @@ class ParetoOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; }; template @@ -222,7 +222,7 @@ bool ParetoOpenList::is_reliable_dead_end( } template -bool ParetoOpenList::pruning_is_safe() const { +bool ParetoOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index b71bca7444..a892345b6a 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -48,7 +48,7 @@ class TieBreakingOpenList : public OpenList { virtual bool is_dead_end(EvaluationContext &eval_context) const override; virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; }; template @@ -142,7 +142,7 @@ bool TieBreakingOpenList::is_reliable_dead_end( } template -bool TieBreakingOpenList::pruning_is_safe() const { +bool TieBreakingOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } diff --git a/src/search/open_lists/type_based_open_list.cc b/src/search/open_lists/type_based_open_list.cc index e22fcb4b5b..7f514ff789 100644 --- a/src/search/open_lists/type_based_open_list.cc +++ b/src/search/open_lists/type_based_open_list.cc @@ -43,7 +43,7 @@ class TypeBasedOpenList : public OpenList { EvaluationContext &eval_context) const override; virtual void get_path_dependent_evaluators( set &evals) override; - virtual bool pruning_is_safe() const override; + virtual bool is_complete() const override; }; template @@ -136,7 +136,7 @@ void TypeBasedOpenList::get_path_dependent_evaluators( } template -bool TypeBasedOpenList::pruning_is_safe() const { +bool TypeBasedOpenList::is_complete() const { /* If at least one of the evaluators ensures that no solvable state is pruned we know that this also holds for TypeBasedOpenList. */ for (const shared_ptr &evaluator : evaluators) { diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index 60dd3a6b87..baf24859b9 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -319,7 +319,7 @@ void EagerSearch::dump_search_space() const { } bool EagerSearch::is_complete() const { - return open_list->pruning_is_safe() && pruning_method->is_safe(); + return open_list->is_complete() && pruning_method->is_safe(); } void EagerSearch::start_f_value_statistics(EvaluationContext &eval_context) { diff --git a/src/search/search_algorithms/lazy_search.cc b/src/search/search_algorithms/lazy_search.cc index 194bdcc99f..fb4189ac3f 100644 --- a/src/search/search_algorithms/lazy_search.cc +++ b/src/search/search_algorithms/lazy_search.cc @@ -236,6 +236,6 @@ void LazySearch::print_statistics() const { } bool LazySearch::is_complete() const { - return open_list->pruning_is_safe(); + return open_list->is_complete(); } } From fb573843b3db732ffc224f563fc023b771697fc7 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 17:53:44 +0100 Subject: [PATCH 15/29] Rename dead_ends_are_reliable to is_safe. --- src/search/evaluator.cc | 2 +- src/search/evaluator.h | 6 +++--- src/search/evaluators/combining_evaluator.cc | 4 ++-- src/search/evaluators/combining_evaluator.h | 5 ++--- src/search/evaluators/weighted_evaluator.cc | 4 ++-- src/search/evaluators/weighted_evaluator.h | 2 +- src/search/heuristics/cea_heuristic.cc | 2 +- src/search/heuristics/cea_heuristic.h | 2 +- src/search/heuristics/cg_heuristic.cc | 2 +- src/search/heuristics/cg_heuristic.h | 2 +- src/search/heuristics/hm_heuristic.cc | 2 +- src/search/heuristics/hm_heuristic.h | 2 +- src/search/heuristics/relaxation_heuristic.cc | 2 +- src/search/heuristics/relaxation_heuristic.h | 2 +- .../landmarks/landmark_cost_partitioning_heuristic.cc | 2 +- src/search/landmarks/landmark_cost_partitioning_heuristic.h | 2 +- src/search/landmarks/landmark_sum_heuristic.cc | 2 +- src/search/landmarks/landmark_sum_heuristic.h | 2 +- src/search/open_lists/best_first_open_list.cc | 4 ++-- src/search/open_lists/epsilon_greedy_open_list.cc | 4 ++-- src/search/open_lists/pareto_open_list.cc | 4 ++-- src/search/open_lists/tiebreaking_open_list.cc | 6 +++--- src/search/open_lists/type_based_open_list.cc | 4 ++-- .../search_algorithms/enforced_hill_climbing_search.cc | 2 +- 24 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/search/evaluator.cc b/src/search/evaluator.cc index bf8287690e..9da9741c6a 100644 --- a/src/search/evaluator.cc +++ b/src/search/evaluator.cc @@ -19,7 +19,7 @@ Evaluator::Evaluator( log(utils::get_log_for_verbosity(verbosity)) { } -bool Evaluator::dead_ends_are_reliable() const { +bool Evaluator::is_safe() const { return true; } diff --git a/src/search/evaluator.h b/src/search/evaluator.h index 81c4c46ddf..f2a0b18816 100644 --- a/src/search/evaluator.h +++ b/src/search/evaluator.h @@ -29,12 +29,12 @@ class Evaluator { virtual ~Evaluator() = default; /* - dead_ends_are_reliable should return true if the evaluator is - "safe", i.e., infinite estimates can be trusted. + is_safe returns true if the evaluator reports dead ends reliably, + i.e., if its infinite estimates can be trusted. The default implementation returns true. */ - virtual bool dead_ends_are_reliable() const; + virtual bool is_safe() const; /* get_path_dependent_evaluators should insert all path-dependent diff --git a/src/search/evaluators/combining_evaluator.cc b/src/search/evaluators/combining_evaluator.cc index 1f6d01b63c..e2293579e2 100644 --- a/src/search/evaluators/combining_evaluator.cc +++ b/src/search/evaluators/combining_evaluator.cc @@ -17,11 +17,11 @@ CombiningEvaluator::CombiningEvaluator( utils::verify_list_not_empty(evals, "evals"); all_dead_ends_are_reliable = true; for (const shared_ptr &subevaluator : subevaluators) - if (!subevaluator->dead_ends_are_reliable()) + if (!subevaluator->is_safe()) all_dead_ends_are_reliable = false; } -bool CombiningEvaluator::dead_ends_are_reliable() const { +bool CombiningEvaluator::is_safe() const { return all_dead_ends_are_reliable; } diff --git a/src/search/evaluators/combining_evaluator.h b/src/search/evaluators/combining_evaluator.h index 81d81c0b0d..d8e2d0098b 100644 --- a/src/search/evaluators/combining_evaluator.h +++ b/src/search/evaluators/combining_evaluator.h @@ -24,7 +24,7 @@ class CombiningEvaluator : public Evaluator { const std::string &description, utils::Verbosity verbosity); /* - Note: dead_ends_are_reliable() is a state-independent method, so + Note: is_safe() is a state-independent method, so it only returns true if all subevaluators report dead ends reliably. Note that we could get more fine-grained information when @@ -35,8 +35,7 @@ class CombiningEvaluator : public Evaluator { way to exploit such state-based information, and hence we do not compute it. */ - - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; virtual EvaluationResult compute_result( EvaluationContext &eval_context) override; diff --git a/src/search/evaluators/weighted_evaluator.cc b/src/search/evaluators/weighted_evaluator.cc index f549c0caed..b2670ded40 100644 --- a/src/search/evaluators/weighted_evaluator.cc +++ b/src/search/evaluators/weighted_evaluator.cc @@ -19,8 +19,8 @@ WeightedEvaluator::WeightedEvaluator( weight(weight) { } -bool WeightedEvaluator::dead_ends_are_reliable() const { - return evaluator->dead_ends_are_reliable(); +bool WeightedEvaluator::is_safe() const { + return evaluator->is_safe(); } EvaluationResult WeightedEvaluator::compute_result( diff --git a/src/search/evaluators/weighted_evaluator.h b/src/search/evaluators/weighted_evaluator.h index 8a8ff45d41..3923b7bb83 100644 --- a/src/search/evaluators/weighted_evaluator.h +++ b/src/search/evaluators/weighted_evaluator.h @@ -19,7 +19,7 @@ class WeightedEvaluator : public Evaluator { const std::shared_ptr &eval, int weight, const std::string &description, utils::Verbosity verbosity); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; virtual EvaluationResult compute_result( EvaluationContext &eval_context) override; virtual void get_path_dependent_evaluators( diff --git a/src/search/heuristics/cea_heuristic.cc b/src/search/heuristics/cea_heuristic.cc index bb7ebdd9b1..6ab561f04b 100644 --- a/src/search/heuristics/cea_heuristic.cc +++ b/src/search/heuristics/cea_heuristic.cc @@ -445,7 +445,7 @@ ContextEnhancedAdditiveHeuristic::~ContextEnhancedAdditiveHeuristic() { delete problem; } -bool ContextEnhancedAdditiveHeuristic::dead_ends_are_reliable() const { +bool ContextEnhancedAdditiveHeuristic::is_safe() const { return false; } diff --git a/src/search/heuristics/cea_heuristic.h b/src/search/heuristics/cea_heuristic.h index 2f91d532da..e816c3fd37 100644 --- a/src/search/heuristics/cea_heuristic.h +++ b/src/search/heuristics/cea_heuristic.h @@ -58,7 +58,7 @@ class ContextEnhancedAdditiveHeuristic : public Heuristic { const std::shared_ptr &transform, bool cache_estimates, const std::string &description, utils::Verbosity verbosity); ~ContextEnhancedAdditiveHeuristic(); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; } diff --git a/src/search/heuristics/cg_heuristic.cc b/src/search/heuristics/cg_heuristic.cc index eb1a1a6872..cc5ec7c1b5 100644 --- a/src/search/heuristics/cg_heuristic.cc +++ b/src/search/heuristics/cg_heuristic.cc @@ -44,7 +44,7 @@ CGHeuristic::CGHeuristic( transition_graphs = factory.build_dtgs(); } -bool CGHeuristic::dead_ends_are_reliable() const { +bool CGHeuristic::is_safe() const { return false; } diff --git a/src/search/heuristics/cg_heuristic.h b/src/search/heuristics/cg_heuristic.h index 40b6a85d14..7e44cd8dbd 100644 --- a/src/search/heuristics/cg_heuristic.h +++ b/src/search/heuristics/cg_heuristic.h @@ -45,7 +45,7 @@ class CGHeuristic : public Heuristic { int max_cache_size, tasks::AxiomHandlingType axiom_hanlding, const std::shared_ptr &transform, bool cache_estimates, const std::string &description, utils::Verbosity verbosity); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; } diff --git a/src/search/heuristics/hm_heuristic.cc b/src/search/heuristics/hm_heuristic.cc index f98ef6c088..ded8e7f9a2 100644 --- a/src/search/heuristics/hm_heuristic.cc +++ b/src/search/heuristics/hm_heuristic.cc @@ -27,7 +27,7 @@ HMHeuristic::HMHeuristic( generate_all_tuples(); } -bool HMHeuristic::dead_ends_are_reliable() const { +bool HMHeuristic::is_safe() const { return !task_properties::has_axioms(task_proxy) && !has_cond_effects; } diff --git a/src/search/heuristics/hm_heuristic.h b/src/search/heuristics/hm_heuristic.h index 9b34b6980f..6f1c1e6b77 100644 --- a/src/search/heuristics/hm_heuristic.h +++ b/src/search/heuristics/hm_heuristic.h @@ -67,7 +67,7 @@ class HMHeuristic : public Heuristic { bool cache_estimates, const std::string &description, utils::Verbosity verbosity); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; } diff --git a/src/search/heuristics/relaxation_heuristic.cc b/src/search/heuristics/relaxation_heuristic.cc index 0e245f3f53..7bea8a1a92 100644 --- a/src/search/heuristics/relaxation_heuristic.cc +++ b/src/search/heuristics/relaxation_heuristic.cc @@ -110,7 +110,7 @@ RelaxationHeuristic::RelaxationHeuristic( } } -bool RelaxationHeuristic::dead_ends_are_reliable() const { +bool RelaxationHeuristic::is_safe() const { return !task_properties::has_axioms(task_proxy); } diff --git a/src/search/heuristics/relaxation_heuristic.h b/src/search/heuristics/relaxation_heuristic.h index 6e2523e1c8..c76f14a0e1 100644 --- a/src/search/heuristics/relaxation_heuristic.h +++ b/src/search/heuristics/relaxation_heuristic.h @@ -116,7 +116,7 @@ class RelaxationHeuristic : public Heuristic { const std::shared_ptr &transform, bool cache_estimates, const std::string &description, utils::Verbosity verbosity); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; extern void add_relaxation_heuristic_options_to_feature( diff --git a/src/search/landmarks/landmark_cost_partitioning_heuristic.cc b/src/search/landmarks/landmark_cost_partitioning_heuristic.cc index a95c026f2d..66de47d0b2 100644 --- a/src/search/landmarks/landmark_cost_partitioning_heuristic.cc +++ b/src/search/landmarks/landmark_cost_partitioning_heuristic.cc @@ -78,7 +78,7 @@ int LandmarkCostPartitioningHeuristic::get_heuristic_value( } } -bool LandmarkCostPartitioningHeuristic::dead_ends_are_reliable() const { +bool LandmarkCostPartitioningHeuristic::is_safe() const { return true; } diff --git a/src/search/landmarks/landmark_cost_partitioning_heuristic.h b/src/search/landmarks/landmark_cost_partitioning_heuristic.h index 344a0a5774..ac9d2824ed 100644 --- a/src/search/landmarks/landmark_cost_partitioning_heuristic.h +++ b/src/search/landmarks/landmark_cost_partitioning_heuristic.h @@ -32,7 +32,7 @@ class LandmarkCostPartitioningHeuristic : public LandmarkHeuristic { CostPartitioningMethod cost_partitioning, bool alm, lp::LPSolverType lpsolver); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; } diff --git a/src/search/landmarks/landmark_sum_heuristic.cc b/src/search/landmarks/landmark_sum_heuristic.cc index a239884cd6..3c7bfd3d77 100644 --- a/src/search/landmarks/landmark_sum_heuristic.cc +++ b/src/search/landmarks/landmark_sum_heuristic.cc @@ -103,7 +103,7 @@ int LandmarkSumHeuristic::get_heuristic_value(const State &ancestor_state) { return h; } -bool LandmarkSumHeuristic::dead_ends_are_reliable() const { +bool LandmarkSumHeuristic::is_safe() const { return dead_ends_reliable; } diff --git a/src/search/landmarks/landmark_sum_heuristic.h b/src/search/landmarks/landmark_sum_heuristic.h index b3ea0f7138..d70daf8114 100644 --- a/src/search/landmarks/landmark_sum_heuristic.h +++ b/src/search/landmarks/landmark_sum_heuristic.h @@ -29,7 +29,7 @@ class LandmarkSumHeuristic : public LandmarkHeuristic { const std::string &description, utils::Verbosity verbosity, tasks::AxiomHandlingType axioms); - virtual bool dead_ends_are_reliable() const override; + virtual bool is_safe() const override; }; } diff --git a/src/search/open_lists/best_first_open_list.cc b/src/search/open_lists/best_first_open_list.cc index 42887b915d..606b564f4e 100644 --- a/src/search/open_lists/best_first_open_list.cc +++ b/src/search/open_lists/best_first_open_list.cc @@ -94,7 +94,7 @@ bool BestFirstOpenList::is_dead_end( template bool BestFirstOpenList::is_reliable_dead_end( EvaluationContext &eval_context) const { - return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); + return is_dead_end(eval_context) && evaluator->is_safe(); } template @@ -102,7 +102,7 @@ bool BestFirstOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } - return evaluator->dead_ends_are_reliable(); + return evaluator->is_safe(); } BestFirstOpenListFactory::BestFirstOpenListFactory( diff --git a/src/search/open_lists/epsilon_greedy_open_list.cc b/src/search/open_lists/epsilon_greedy_open_list.cc index 072ec74fc8..25855ccabf 100644 --- a/src/search/open_lists/epsilon_greedy_open_list.cc +++ b/src/search/open_lists/epsilon_greedy_open_list.cc @@ -117,7 +117,7 @@ bool EpsilonGreedyOpenList::is_dead_end( template bool EpsilonGreedyOpenList::is_reliable_dead_end( EvaluationContext &eval_context) const { - return is_dead_end(eval_context) && evaluator->dead_ends_are_reliable(); + return is_dead_end(eval_context) && evaluator->is_safe(); } template @@ -125,7 +125,7 @@ bool EpsilonGreedyOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } - return evaluator->dead_ends_are_reliable(); + return evaluator->is_safe(); } template diff --git a/src/search/open_lists/pareto_open_list.cc b/src/search/open_lists/pareto_open_list.cc index f9b4bd8e11..d6cf03a73c 100644 --- a/src/search/open_lists/pareto_open_list.cc +++ b/src/search/open_lists/pareto_open_list.cc @@ -216,7 +216,7 @@ bool ParetoOpenList::is_reliable_dead_end( EvaluationContext &eval_context) const { for (const shared_ptr &evaluator : evaluators) if (eval_context.is_evaluator_value_infinite(evaluator.get()) && - evaluator->dead_ends_are_reliable()) + evaluator->is_safe()) return true; return false; } @@ -229,7 +229,7 @@ bool ParetoOpenList::is_complete() const { /* If at least one of the evaluators ensures that no solvable state is pruned we know that this also holds for ParetoOpenList. */ for (const shared_ptr &evaluator : evaluators) { - if (evaluator->dead_ends_are_reliable()) { + if (evaluator->is_safe()) { return true; } } diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index a892345b6a..15b7be784d 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -136,7 +136,7 @@ bool TieBreakingOpenList::is_reliable_dead_end( EvaluationContext &eval_context) const { for (const shared_ptr &evaluator : evaluators) if (eval_context.is_evaluator_value_infinite(evaluator.get()) && - evaluator->dead_ends_are_reliable()) + evaluator->is_safe()) return true; return false; } @@ -147,7 +147,7 @@ bool TieBreakingOpenList::is_complete() const { return false; } assert(!evaluators.empty()); - if (evaluators[0]->dead_ends_are_reliable()) { + if (evaluators[0]->is_safe()) { return true; } // At this point we know that the first evaluator is unsafe. @@ -160,7 +160,7 @@ bool TieBreakingOpenList::is_complete() const { one other evaluator is safe. */ for (const shared_ptr &evaluator : evaluators) { - if (evaluator->dead_ends_are_reliable()) { + if (evaluator->is_safe()) { return true; } } diff --git a/src/search/open_lists/type_based_open_list.cc b/src/search/open_lists/type_based_open_list.cc index 7f514ff789..f1735d592b 100644 --- a/src/search/open_lists/type_based_open_list.cc +++ b/src/search/open_lists/type_based_open_list.cc @@ -120,7 +120,7 @@ template bool TypeBasedOpenList::is_reliable_dead_end( EvaluationContext &eval_context) const { for (const shared_ptr &evaluator : evaluators) { - if (evaluator->dead_ends_are_reliable() && + if (evaluator->is_safe() && eval_context.is_evaluator_value_infinite(evaluator.get())) return true; } @@ -140,7 +140,7 @@ bool TypeBasedOpenList::is_complete() const { /* If at least one of the evaluators ensures that no solvable state is pruned we know that this also holds for TypeBasedOpenList. */ for (const shared_ptr &evaluator : evaluators) { - if (evaluator->dead_ends_are_reliable()) { + if (evaluator->is_safe()) { return true; } } diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.cc b/src/search/search_algorithms/enforced_hill_climbing_search.cc index b1d0ef7495..bf19e3e724 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.cc +++ b/src/search/search_algorithms/enforced_hill_climbing_search.cc @@ -106,7 +106,7 @@ void EnforcedHillClimbingSearch::initialize() { if (dead_end) { log << "Initial state is a dead end, no solution" << endl; - if (evaluator->dead_ends_are_reliable()) + if (evaluator->is_safe()) utils::exit_with(ExitCode::SEARCH_UNSOLVABLE); else utils::exit_with(ExitCode::SEARCH_UNSOLVED_INCOMPLETE); From 0708d6ba484afe1d166d26fd51a24d284ba5bcf8 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Thu, 12 Feb 2026 17:57:41 +0100 Subject: [PATCH 16/29] Add some comments. --- src/search/pruning_method.h | 2 ++ src/search/search_algorithm.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/search/pruning_method.h b/src/search/pruning_method.h index 63e48d6224..11078e54b9 100644 --- a/src/search/pruning_method.h +++ b/src/search/pruning_method.h @@ -37,6 +37,8 @@ class PruningMethod { virtual void initialize(const std::shared_ptr &task); void prune_operators(const State &state, std::vector &op_ids); virtual void print_statistics() const; + + // is_safe returns true if no solvable state can be pruned. virtual bool is_safe() const = 0; }; diff --git a/src/search/search_algorithm.h b/src/search/search_algorithm.h index b6892c1f94..a0ab783951 100644 --- a/src/search/search_algorithm.h +++ b/src/search/search_algorithm.h @@ -77,6 +77,10 @@ class SearchAlgorithm { virtual ~SearchAlgorithm(); virtual void print_statistics() const = 0; virtual void save_plan_if_necessary(); + /* + is_complete returns true if the search algorithm cannot "overlook" any + solvable state. + */ virtual bool is_complete() const = 0; bool found_solution() const; SearchStatus get_status() const; From a9e3c8248088e945adcb5b3892cbc3735bf63ab4 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 13 Feb 2026 12:28:41 +0100 Subject: [PATCH 17/29] Use lambdas and adjust code comments. --- src/search/open_list.h | 5 ++--- src/search/open_lists/alternation_open_list.cc | 9 +++++++-- src/search/open_lists/pareto_open_list.cc | 11 +++-------- src/search/open_lists/tiebreaking_open_list.cc | 12 ++++-------- src/search/open_lists/type_based_open_list.cc | 10 ++-------- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/search/open_list.h b/src/search/open_list.h index 23b76b32df..19bee62545 100644 --- a/src/search/open_list.h +++ b/src/search/open_list.h @@ -123,9 +123,8 @@ class OpenList { virtual bool is_reliable_dead_end( EvaluationContext &eval_context) const = 0; /* - is_complete returns true if the open list cannot "overlook" any solvable - state. Thus, if the open list is "complete" then it can be used to detect - unsolvability of a task. + If for some solvable task no reachable goal state is ever inserted + into the open list then it is not complete, i.e. is_complete is false. */ virtual bool is_complete() const = 0; }; diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index 85cf49ec50..b3a0be51c8 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -8,6 +8,7 @@ #include #include +#include #include using namespace std; @@ -129,12 +130,16 @@ template bool AlternationOpenList::is_complete() const { /* If at least one of the sub-lists ensures that no solvable state is pruned we know that this also holds for AlternationOpenList. */ - for (const auto &sublist : open_lists) { + /* for (const auto &sublist : open_lists) { if (sublist->is_complete()) { return true; } } - return false; + return false; */ + auto is_complete = [](const auto &sublist) { + return sublist->is_complete(); + }; + return ranges::any_of(open_lists, is_complete); } AlternationOpenListFactory::AlternationOpenListFactory( diff --git a/src/search/open_lists/pareto_open_list.cc b/src/search/open_lists/pareto_open_list.cc index d6cf03a73c..f1b4d20a42 100644 --- a/src/search/open_lists/pareto_open_list.cc +++ b/src/search/open_lists/pareto_open_list.cc @@ -8,6 +8,7 @@ #include "../utils/rng.h" #include "../utils/rng_options.h" +#include #include #include #include @@ -226,14 +227,8 @@ bool ParetoOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } - /* If at least one of the evaluators ensures that no solvable state - is pruned we know that this also holds for ParetoOpenList. */ - for (const shared_ptr &evaluator : evaluators) { - if (evaluator->is_safe()) { - return true; - } - } - return false; + auto is_safe = [](const auto &evaluator) { return evaluator->is_safe(); }; + return ranges::any_of(evaluators, is_safe); } ParetoOpenListFactory::ParetoOpenListFactory( diff --git a/src/search/open_lists/tiebreaking_open_list.cc b/src/search/open_lists/tiebreaking_open_list.cc index 15b7be784d..ce1aad8df5 100644 --- a/src/search/open_lists/tiebreaking_open_list.cc +++ b/src/search/open_lists/tiebreaking_open_list.cc @@ -155,16 +155,12 @@ bool TieBreakingOpenList::is_complete() const { return false; } /* - Even if the first evaluator is unsafe we can still say that - pruning is safe if (allow_unsafe_pruning is false and) at least + Even if the first evaluator is unsafe we can still ensure + completeness if (allow_unsafe_pruning is false and) at least one other evaluator is safe. */ - for (const shared_ptr &evaluator : evaluators) { - if (evaluator->is_safe()) { - return true; - } - } - return false; + auto is_safe = [](const auto &evaluator) { return evaluator->is_safe(); }; + return ranges::any_of(evaluators, is_safe); } TieBreakingOpenListFactory::TieBreakingOpenListFactory( diff --git a/src/search/open_lists/type_based_open_list.cc b/src/search/open_lists/type_based_open_list.cc index f1735d592b..d10f72325c 100644 --- a/src/search/open_lists/type_based_open_list.cc +++ b/src/search/open_lists/type_based_open_list.cc @@ -137,14 +137,8 @@ void TypeBasedOpenList::get_path_dependent_evaluators( template bool TypeBasedOpenList::is_complete() const { - /* If at least one of the evaluators ensures that no solvable state - is pruned we know that this also holds for TypeBasedOpenList. */ - for (const shared_ptr &evaluator : evaluators) { - if (evaluator->is_safe()) { - return true; - } - } - return false; + auto is_safe = [](const auto &evaluator) { return evaluator->is_safe(); }; + return ranges::any_of(evaluators, is_safe); } TypeBasedOpenListFactory::TypeBasedOpenListFactory( From b3e1d66cdc958cf29bf858d10c667552cd96a988 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 13 Feb 2026 16:34:08 +0100 Subject: [PATCH 18/29] Add exit code and search status UNSOLVABLE_WITHIN_BOUND. --- src/search/planner.cc | 11 ++++++++--- src/search/search_algorithm.cc | 12 ++++++++++++ src/search/search_algorithm.h | 11 +++++++++-- src/search/search_algorithms/eager_search.cc | 3 +-- src/search/search_algorithms/iterated_search.cc | 5 +++-- src/search/search_algorithms/lazy_search.cc | 2 +- src/search/utils/system.cc | 3 +++ src/search/utils/system.h | 5 +++-- 8 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/search/planner.cc b/src/search/planner.cc index a609daf508..5a5d6ca79d 100644 --- a/src/search/planner.cc +++ b/src/search/planner.cc @@ -8,6 +8,7 @@ #include "utils/system.h" #include "utils/timer.h" +#include #include using namespace std; @@ -53,14 +54,18 @@ int main(int argc, const char **argv) { utils::g_log << "Search time: " << search_timer << endl; utils::g_log << "Total time: " << utils::g_timer << endl; + SearchStatus search_status = search_algorithm->get_status(); ExitCode exitcode; - if (search_algorithm->found_solution()) { + if (search_status == SearchStatus::SOLVED) { exitcode = ExitCode::SUCCESS; - } else if (search_algorithm->get_status() == SearchStatus::TIMEOUT) { + } else if (search_status == SearchStatus::TIMEOUT) { exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; - } else if (search_algorithm->is_complete()) { + } else if (search_status == SearchStatus::UNSOLVABLE) { exitcode = ExitCode::SEARCH_UNSOLVABLE; + } else if (search_status == SearchStatus::UNSOLVABLE_WITHIN_BOUND) { + exitcode = ExitCode::SEARCH_UNSOLVABLE_WITHIN_BOUND; } else { + assert(search_status == SearchStatus::FAILED); exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; } exit_with(exitcode); diff --git a/src/search/search_algorithm.cc b/src/search/search_algorithm.cc index cb37b479e7..eb4f90fbce 100644 --- a/src/search/search_algorithm.cc +++ b/src/search/search_algorithm.cc @@ -99,6 +99,18 @@ SearchStatus SearchAlgorithm::get_status() const { return status; } +SearchStatus SearchAlgorithm::get_finished_search_status() const { + if (found_solution()) { + return SOLVED; + } else if (is_unbounded() && is_complete()) { + return UNSOLVABLE; + } else if (is_complete()) { + return UNSOLVABLE_WITHIN_BOUND; + } else { + return FAILED; + } +} + const Plan &SearchAlgorithm::get_plan() const { assert(solution_found); return plan; diff --git a/src/search/search_algorithm.h b/src/search/search_algorithm.h index a0ab783951..049edc138d 100644 --- a/src/search/search_algorithm.h +++ b/src/search/search_algorithm.h @@ -12,6 +12,7 @@ #include "utils/logging.h" +#include #include namespace plugins { @@ -32,6 +33,8 @@ enum SearchStatus { IN_PROGRESS, TIMEOUT, FAILED, + UNSOLVABLE, + UNSOLVABLE_WITHIN_BOUND, SOLVED }; @@ -78,12 +81,13 @@ class SearchAlgorithm { virtual void print_statistics() const = 0; virtual void save_plan_if_necessary(); /* - is_complete returns true if the search algorithm cannot "overlook" any - solvable state. + is_complete returns true if the search algorithm finds a plan + within the bound if a plan exists. */ virtual bool is_complete() const = 0; bool found_solution() const; SearchStatus get_status() const; + SearchStatus get_finished_search_status() const; const Plan &get_plan() const; void search(); const SearchStatistics &get_statistics() const { @@ -95,6 +99,9 @@ class SearchAlgorithm { int get_bound() { return bound; } + bool is_unbounded() const { + return bound == std::numeric_limits::max(); + } PlanManager &get_plan_manager() { return plan_manager; } diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index baf24859b9..d948b9fea3 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -118,9 +118,8 @@ SearchStatus EagerSearch::step() { if (!node.has_value()) { assert(open_list->empty()); log << "Completely explored state space -- no solution!" << endl; - return FAILED; + return get_finished_search_status(); } - return expand(node.value()); } diff --git a/src/search/search_algorithms/iterated_search.cc b/src/search/search_algorithms/iterated_search.cc index ef72dc8921..1cabcdad19 100644 --- a/src/search/search_algorithms/iterated_search.cc +++ b/src/search/search_algorithms/iterated_search.cc @@ -63,7 +63,7 @@ shared_ptr IteratedSearch::create_current_phase() { SearchStatus IteratedSearch::step() { shared_ptr current_search = create_current_phase(); if (!current_search) { - return found_solution() ? SOLVED : FAILED; + return get_finished_search_status(); } if (pass_bound && best_bound < current_search->get_bound()) { current_search->set_bound(best_bound); @@ -116,7 +116,7 @@ SearchStatus IteratedSearch::step_return_value() { return IN_PROGRESS; } else { log << "No solution found - stop searching" << endl; - return iterated_found_solution ? SOLVED : FAILED; + return get_finished_search_status(); } } } @@ -132,6 +132,7 @@ void IteratedSearch::save_plan_if_necessary() { } bool IteratedSearch::is_complete() const { + // TODO adjust to relative completeness of sub-searches /* TODO - return true if the first search algorithm is complete diff --git a/src/search/search_algorithms/lazy_search.cc b/src/search/search_algorithms/lazy_search.cc index fb4189ac3f..9b0437e93d 100644 --- a/src/search/search_algorithms/lazy_search.cc +++ b/src/search/search_algorithms/lazy_search.cc @@ -122,7 +122,7 @@ void LazySearch::generate_successors() { SearchStatus LazySearch::fetch_next_state() { if (open_list->empty()) { log << "Completely explored state space -- no solution!" << endl; - return FAILED; + return get_finished_search_status(); } EdgeOpenListEntry next = open_list->remove_min(); diff --git a/src/search/utils/system.cc b/src/search/utils/system.cc index 46a2438908..59f6eee3b9 100644 --- a/src/search/utils/system.cc +++ b/src/search/utils/system.cc @@ -19,6 +19,8 @@ const char *get_exit_code_message_reentrant(ExitCode exitcode) { return "Task is provably unsolvable."; case ExitCode::SEARCH_UNSOLVED_INCOMPLETE: return "Search stopped without finding a solution."; + case ExitCode::SEARCH_UNSOLVABLE_WITHIN_BOUND: + return "Task is provably unsolvable within the given bound."; case ExitCode::SEARCH_OUT_OF_MEMORY: return "Memory limit has been reached."; case ExitCode::SEARCH_OUT_OF_TIME: @@ -33,6 +35,7 @@ bool is_exit_code_error_reentrant(ExitCode exitcode) { case ExitCode::SUCCESS: case ExitCode::SEARCH_UNSOLVABLE: case ExitCode::SEARCH_UNSOLVED_INCOMPLETE: + case ExitCode::SEARCH_UNSOLVABLE_WITHIN_BOUND: case ExitCode::SEARCH_OUT_OF_MEMORY: case ExitCode::SEARCH_OUT_OF_TIME: return false; diff --git a/src/search/utils/system.h b/src/search/utils/system.h index bd50f550d9..04a20a79d2 100644 --- a/src/search/utils/system.h +++ b/src/search/utils/system.h @@ -37,8 +37,9 @@ enum class ExitCode { SUCCESS = 0, // 10-19: exit codes denoting no plan was found (without any error) - SEARCH_UNSOLVABLE = 11, // Task is provably unsolvable with given bound. - SEARCH_UNSOLVED_INCOMPLETE = 12, // Search ended without finding a solution. + SEARCH_UNSOLVABLE = 11, + SEARCH_UNSOLVED_INCOMPLETE = 12, + SEARCH_UNSOLVABLE_WITHIN_BOUND = 13, // 20-29: "expected" failures SEARCH_OUT_OF_MEMORY = 22, From 54e493956d9c20ddbb0bf2edd1cb2f0910667651 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 13 Feb 2026 16:47:05 +0100 Subject: [PATCH 19/29] Rename search algorithm's is_complete to is_complete_within_bound. --- src/search/search_algorithm.cc | 4 ++-- src/search/search_algorithm.h | 6 +++--- src/search/search_algorithms/eager_search.cc | 2 +- src/search/search_algorithms/eager_search.h | 2 +- .../search_algorithms/enforced_hill_climbing_search.cc | 2 +- .../search_algorithms/enforced_hill_climbing_search.h | 2 +- src/search/search_algorithms/iterated_search.cc | 2 +- src/search/search_algorithms/iterated_search.h | 2 +- src/search/search_algorithms/lazy_search.cc | 2 +- src/search/search_algorithms/lazy_search.h | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/search/search_algorithm.cc b/src/search/search_algorithm.cc index eb4f90fbce..7065befd7d 100644 --- a/src/search/search_algorithm.cc +++ b/src/search/search_algorithm.cc @@ -102,9 +102,9 @@ SearchStatus SearchAlgorithm::get_status() const { SearchStatus SearchAlgorithm::get_finished_search_status() const { if (found_solution()) { return SOLVED; - } else if (is_unbounded() && is_complete()) { + } else if (is_unbounded() && is_complete_within_bound()) { return UNSOLVABLE; - } else if (is_complete()) { + } else if (is_complete_within_bound()) { return UNSOLVABLE_WITHIN_BOUND; } else { return FAILED; diff --git a/src/search/search_algorithm.h b/src/search/search_algorithm.h index 049edc138d..d9da31dc07 100644 --- a/src/search/search_algorithm.h +++ b/src/search/search_algorithm.h @@ -81,10 +81,10 @@ class SearchAlgorithm { virtual void print_statistics() const = 0; virtual void save_plan_if_necessary(); /* - is_complete returns true if the search algorithm finds a plan - within the bound if a plan exists. + is_complete_within_bound returns true if the search algorithm finds + a plan within the bound if a plan exists. */ - virtual bool is_complete() const = 0; + virtual bool is_complete_within_bound() const = 0; bool found_solution() const; SearchStatus get_status() const; SearchStatus get_finished_search_status() const; diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index d948b9fea3..79c40bf745 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -317,7 +317,7 @@ void EagerSearch::dump_search_space() const { search_space.dump(task_proxy); } -bool EagerSearch::is_complete() const { +bool EagerSearch::is_complete_within_bound() const { return open_list->is_complete() && pruning_method->is_safe(); } diff --git a/src/search/search_algorithms/eager_search.h b/src/search/search_algorithms/eager_search.h index adb300cccc..825a38ad35 100644 --- a/src/search/search_algorithms/eager_search.h +++ b/src/search/search_algorithms/eager_search.h @@ -57,7 +57,7 @@ class EagerSearch : public SearchAlgorithm { virtual void print_statistics() const override; void dump_search_space() const; - virtual bool is_complete() const override; + virtual bool is_complete_within_bound() const override; }; extern void add_eager_search_options_to_feature( diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.cc b/src/search/search_algorithms/enforced_hill_climbing_search.cc index bf19e3e724..609fe9f106 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.cc +++ b/src/search/search_algorithms/enforced_hill_climbing_search.cc @@ -259,7 +259,7 @@ void EnforcedHillClimbingSearch::print_statistics() const { } } -bool EnforcedHillClimbingSearch::is_complete() const { +bool EnforcedHillClimbingSearch::is_complete_within_bound() const { // Enforced hill climbing searches cannot guarantee completeness. return false; } diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.h b/src/search/search_algorithms/enforced_hill_climbing_search.h index 79095b04f8..55b81f9871 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.h +++ b/src/search/search_algorithms/enforced_hill_climbing_search.h @@ -64,7 +64,7 @@ class EnforcedHillClimbingSearch : public SearchAlgorithm { const std::string &description, utils::Verbosity verbosity); virtual void print_statistics() const override; - virtual bool is_complete() const override; + virtual bool is_complete_within_bound() const override; }; } diff --git a/src/search/search_algorithms/iterated_search.cc b/src/search/search_algorithms/iterated_search.cc index 1cabcdad19..eea13a0c34 100644 --- a/src/search/search_algorithms/iterated_search.cc +++ b/src/search/search_algorithms/iterated_search.cc @@ -131,7 +131,7 @@ void IteratedSearch::save_plan_if_necessary() { // each successful search iteration. } -bool IteratedSearch::is_complete() const { +bool IteratedSearch::is_complete_within_bound() const { // TODO adjust to relative completeness of sub-searches /* TODO diff --git a/src/search/search_algorithms/iterated_search.h b/src/search/search_algorithms/iterated_search.h index c5de1775df..202dd81162 100644 --- a/src/search/search_algorithms/iterated_search.h +++ b/src/search/search_algorithms/iterated_search.h @@ -36,7 +36,7 @@ class IteratedSearch : public SearchAlgorithm { virtual void save_plan_if_necessary() override; virtual void print_statistics() const override; - virtual bool is_complete() const override; + virtual bool is_complete_within_bound() const override; }; } diff --git a/src/search/search_algorithms/lazy_search.cc b/src/search/search_algorithms/lazy_search.cc index 9b0437e93d..db8480bdde 100644 --- a/src/search/search_algorithms/lazy_search.cc +++ b/src/search/search_algorithms/lazy_search.cc @@ -235,7 +235,7 @@ void LazySearch::print_statistics() const { search_space.print_statistics(); } -bool LazySearch::is_complete() const { +bool LazySearch::is_complete_within_bound() const { return open_list->is_complete(); } } diff --git a/src/search/search_algorithms/lazy_search.h b/src/search/search_algorithms/lazy_search.h index 206f70e37c..8d0743da91 100644 --- a/src/search/search_algorithms/lazy_search.h +++ b/src/search/search_algorithms/lazy_search.h @@ -58,7 +58,7 @@ class LazySearch : public SearchAlgorithm { const std::string &description, utils::Verbosity verbosity); virtual void print_statistics() const override; - virtual bool is_complete() const override; + virtual bool is_complete_within_bound() const override; }; } From 68314af23dcb408dbd47b19fc29db9d44d3f2c3c Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 13 Feb 2026 17:04:30 +0100 Subject: [PATCH 20/29] Fix draft of IteratedSearch::is_complete(). --- src/search/search_algorithms/iterated_search.cc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/search/search_algorithms/iterated_search.cc b/src/search/search_algorithms/iterated_search.cc index eea13a0c34..e80068dc38 100644 --- a/src/search/search_algorithms/iterated_search.cc +++ b/src/search/search_algorithms/iterated_search.cc @@ -132,17 +132,18 @@ void IteratedSearch::save_plan_if_necessary() { } bool IteratedSearch::is_complete_within_bound() const { - // TODO adjust to relative completeness of sub-searches /* TODO - - return true if the first search algorithm is complete - - otherwise, return false if continue_on_fail == false (and the first - search algorithm is not complete) + - return true if the first search algorithm is complete and its bound is + greater or equal to that of IteratedSearch, i.e. if it is complete + within the bound of IteratedSearch + - otherwise (the first search algorithm is not complete within + IteratedSearch's bound), return false if continue_on_fail == false - otherwise (continue_on_fail == true and first search algorithm is not - complete) return true if at least one (other) search algorithm is - complete + complete within IteratedSearch's bound), return true if at least one + (other) search algorithm is complete within IteratedSearch's bound - otherwise (continue_on_fail == true and all search algorithms are not - complete) return false + complete within IteratedSearch's bound), return false Before we solve the component interaction problem, we cannot access the necessary information from within a constant function. From 0a50aca103cae150c7d538a91076e9931b352f4a Mon Sep 17 00:00:00 2001 From: Simon Dold Date: Sun, 15 Feb 2026 15:34:30 +0100 Subject: [PATCH 21/29] add exit code in docs. --- docs/exit-codes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/exit-codes.md b/docs/exit-codes.md index ba612ca881..0a15ca8963 100644 --- a/docs/exit-codes.md +++ b/docs/exit-codes.md @@ -25,8 +25,9 @@ termination which prevents the execution of further components. | **Code** | **Name** | **Meaning** | | -------- | -------- | ----------- | | 10 | TRANSLATE_UNSOLVABLE | Translator proved task to be unsolvable. Currently not used. | -| 11 | SEARCH_UNSOLVABLE | Task is provably unsolvable with current bound. | +| 11 | SEARCH_UNSOLVABLE | Task is provably unsolvable. | | 12 | SEARCH_UNSOLVED_INCOMPLETE | Search ended without finding a solution. | +| 13 | SEARCH_UNSOLVABLE_WITHIN_BOUND | Task is provably unsolvable with current bound. | The third block (20-29) represents expected failures which prevent the execution of further components. From 95936bd2ad0b005f9a4024ab7d77210d3290bc51 Mon Sep 17 00:00:00 2001 From: Simon Dold Date: Sun, 15 Feb 2026 15:52:10 +0100 Subject: [PATCH 22/29] minor renaming. --- src/search/evaluators/combining_evaluator.cc | 6 +++--- src/search/evaluators/combining_evaluator.h | 8 ++++---- src/search/open_lists/alternation_open_list.cc | 12 ++---------- src/search/open_lists/pareto_open_list.cc | 7 ++++--- src/search/open_lists/type_based_open_list.cc | 6 ++++-- .../enforced_hill_climbing_search.cc | 1 - src/search/search_algorithms/iterated_search.cc | 2 +- 7 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/search/evaluators/combining_evaluator.cc b/src/search/evaluators/combining_evaluator.cc index e2293579e2..46002a1f23 100644 --- a/src/search/evaluators/combining_evaluator.cc +++ b/src/search/evaluators/combining_evaluator.cc @@ -15,14 +15,14 @@ CombiningEvaluator::CombiningEvaluator( : Evaluator(false, false, false, description, verbosity), subevaluators(evals) { utils::verify_list_not_empty(evals, "evals"); - all_dead_ends_are_reliable = true; + all_subevaluators_are_safe = true; for (const shared_ptr &subevaluator : subevaluators) if (!subevaluator->is_safe()) - all_dead_ends_are_reliable = false; + all_subevaluators_are_safe = false; } bool CombiningEvaluator::is_safe() const { - return all_dead_ends_are_reliable; + return all_subevaluators_are_safe; } EvaluationResult CombiningEvaluator::compute_result( diff --git a/src/search/evaluators/combining_evaluator.h b/src/search/evaluators/combining_evaluator.h index d8e2d0098b..211975f8c7 100644 --- a/src/search/evaluators/combining_evaluator.h +++ b/src/search/evaluators/combining_evaluator.h @@ -15,7 +15,7 @@ namespace combining_evaluator { */ class CombiningEvaluator : public Evaluator { std::vector> subevaluators; - bool all_dead_ends_are_reliable; + bool all_subevaluators_are_safe; protected: virtual int combine_values(const std::vector &values) = 0; public: @@ -25,13 +25,13 @@ class CombiningEvaluator : public Evaluator { /* Note: is_safe() is a state-independent method, so - it only returns true if all subevaluators report dead ends reliably. + it only returns true if all subevaluators are safe. Note that we could get more fine-grained information when considering of reliability for a given evaluated state. For - example, if we use h1 (unreliable) and h2 (reliable) and have a + example, if we use h1 (unsafe) and h2 (safe) and have a state where h1 is finite and h2 is infinite, then we can - *reliably* mark the state as a dead end. There is currently no + *safely* mark the state as a dead end. There is currently no way to exploit such state-based information, and hence we do not compute it. */ diff --git a/src/search/open_lists/alternation_open_list.cc b/src/search/open_lists/alternation_open_list.cc index b3a0be51c8..ac250e31f5 100644 --- a/src/search/open_lists/alternation_open_list.cc +++ b/src/search/open_lists/alternation_open_list.cc @@ -128,18 +128,10 @@ bool AlternationOpenList::is_reliable_dead_end( template bool AlternationOpenList::is_complete() const { - /* If at least one of the sub-lists ensures that no solvable state is - pruned we know that this also holds for AlternationOpenList. */ - /* for (const auto &sublist : open_lists) { - if (sublist->is_complete()) { - return true; - } - } - return false; */ - auto is_complete = [](const auto &sublist) { + auto is_sublist_complete = [](const auto &sublist) { return sublist->is_complete(); }; - return ranges::any_of(open_lists, is_complete); + return ranges::any_of(open_lists, is_sublist_complete); } AlternationOpenListFactory::AlternationOpenListFactory( diff --git a/src/search/open_lists/pareto_open_list.cc b/src/search/open_lists/pareto_open_list.cc index f1b4d20a42..b50b5983cc 100644 --- a/src/search/open_lists/pareto_open_list.cc +++ b/src/search/open_lists/pareto_open_list.cc @@ -8,7 +8,6 @@ #include "../utils/rng.h" #include "../utils/rng_options.h" -#include #include #include #include @@ -227,8 +226,10 @@ bool ParetoOpenList::is_complete() const { if (this->only_contains_preferred_entries()) { return false; } - auto is_safe = [](const auto &evaluator) { return evaluator->is_safe(); }; - return ranges::any_of(evaluators, is_safe); + auto is_evaluator_safe = [](const auto &evaluator) { + return evaluator->is_safe(); + }; + return ranges::any_of(evaluators, is_evaluator_safe); } ParetoOpenListFactory::ParetoOpenListFactory( diff --git a/src/search/open_lists/type_based_open_list.cc b/src/search/open_lists/type_based_open_list.cc index d10f72325c..774ec68c82 100644 --- a/src/search/open_lists/type_based_open_list.cc +++ b/src/search/open_lists/type_based_open_list.cc @@ -137,8 +137,10 @@ void TypeBasedOpenList::get_path_dependent_evaluators( template bool TypeBasedOpenList::is_complete() const { - auto is_safe = [](const auto &evaluator) { return evaluator->is_safe(); }; - return ranges::any_of(evaluators, is_safe); + auto is_evaluator_safe = [](const auto &evaluator) { + return evaluator->is_safe(); + }; + return ranges::any_of(evaluators, is_evaluator_safe); } TypeBasedOpenListFactory::TypeBasedOpenListFactory( diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.cc b/src/search/search_algorithms/enforced_hill_climbing_search.cc index 609fe9f106..c302324d42 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.cc +++ b/src/search/search_algorithms/enforced_hill_climbing_search.cc @@ -260,7 +260,6 @@ void EnforcedHillClimbingSearch::print_statistics() const { } bool EnforcedHillClimbingSearch::is_complete_within_bound() const { - // Enforced hill climbing searches cannot guarantee completeness. return false; } diff --git a/src/search/search_algorithms/iterated_search.cc b/src/search/search_algorithms/iterated_search.cc index e80068dc38..b1ff19c926 100644 --- a/src/search/search_algorithms/iterated_search.cc +++ b/src/search/search_algorithms/iterated_search.cc @@ -145,7 +145,7 @@ bool IteratedSearch::is_complete_within_bound() const { - otherwise (continue_on_fail == true and all search algorithms are not complete within IteratedSearch's bound), return false - Before we solve the component interaction problem, we cannot + Before we solve the component interaction problem (issue559), we cannot access the necessary information from within a constant function. Once we can implement this function we also want to change IteratedSearch From 927dc0b83bcac3abc5ea621bf0e787c67598acad Mon Sep 17 00:00:00 2001 From: Simon Dold Date: Sun, 15 Feb 2026 15:52:59 +0100 Subject: [PATCH 23/29] add missing is_safe check to const evaluator. --- src/search/evaluators/const_evaluator.cc | 4 ++++ src/search/evaluators/const_evaluator.h | 1 + 2 files changed, 5 insertions(+) diff --git a/src/search/evaluators/const_evaluator.cc b/src/search/evaluators/const_evaluator.cc index e3f10e3c2d..6ed0a260ec 100644 --- a/src/search/evaluators/const_evaluator.cc +++ b/src/search/evaluators/const_evaluator.cc @@ -15,6 +15,10 @@ EvaluationResult ConstEvaluator::compute_result(EvaluationContext &) { result.set_evaluator_value(value); return result; } +bool ConstEvaluator::is_safe() const { + int infinity = std::numeric_limits::max(); + return value < infinity; +} class ConstEvaluatorFeature : public plugins::TypedFeature { diff --git a/src/search/evaluators/const_evaluator.h b/src/search/evaluators/const_evaluator.h index bb90ae737d..0a37249488 100644 --- a/src/search/evaluators/const_evaluator.h +++ b/src/search/evaluators/const_evaluator.h @@ -21,6 +21,7 @@ class ConstEvaluator : public Evaluator { virtual void get_path_dependent_evaluators( std::set &) override { } + virtual bool is_safe() const override; }; } From f35111db9fa7cae65a1db1033552771e19148790 Mon Sep 17 00:00:00 2001 From: SimonDold <48084373+SimonDold@users.noreply.github.com> Date: Sun, 15 Feb 2026 18:17:35 +0100 Subject: [PATCH 24/29] remove std. --- src/search/evaluators/const_evaluator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search/evaluators/const_evaluator.cc b/src/search/evaluators/const_evaluator.cc index 6ed0a260ec..33c3313ed8 100644 --- a/src/search/evaluators/const_evaluator.cc +++ b/src/search/evaluators/const_evaluator.cc @@ -16,7 +16,7 @@ EvaluationResult ConstEvaluator::compute_result(EvaluationContext &) { return result; } bool ConstEvaluator::is_safe() const { - int infinity = std::numeric_limits::max(); + int infinity = numeric_limits::max(); return value < infinity; } From 9d9c5a7c9eba46384a83e65bc4424b176fbfc2fe Mon Sep 17 00:00:00 2001 From: Simon Dold Date: Mon, 16 Feb 2026 13:44:05 +0100 Subject: [PATCH 25/29] add infty check for const eval and weighted eval. --- src/search/evaluators/const_evaluator.cc | 5 +++-- src/search/evaluators/weighted_evaluator.cc | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/search/evaluators/const_evaluator.cc b/src/search/evaluators/const_evaluator.cc index 33c3313ed8..7c89ab6b55 100644 --- a/src/search/evaluators/const_evaluator.cc +++ b/src/search/evaluators/const_evaluator.cc @@ -1,5 +1,7 @@ #include "const_evaluator.h" +#include "../evaluation_result.h" + #include "../plugins/plugin.h" using namespace std; @@ -16,8 +18,7 @@ EvaluationResult ConstEvaluator::compute_result(EvaluationContext &) { return result; } bool ConstEvaluator::is_safe() const { - int infinity = numeric_limits::max(); - return value < infinity; + return value < EvaluationResult::INFTY; } class ConstEvaluatorFeature diff --git a/src/search/evaluators/weighted_evaluator.cc b/src/search/evaluators/weighted_evaluator.cc index b2670ded40..f1294c80ba 100644 --- a/src/search/evaluators/weighted_evaluator.cc +++ b/src/search/evaluators/weighted_evaluator.cc @@ -20,6 +20,12 @@ WeightedEvaluator::WeightedEvaluator( } bool WeightedEvaluator::is_safe() const { + if (weight == 0) { + return true; + } + if (weight == EvaluationResult::INFTY) { + return false; + } return evaluator->is_safe(); } From 305ab66cc15140ace1fea2080019c70690d8fedf Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 20 Feb 2026 09:56:05 +0100 Subject: [PATCH 26/29] Change if-else to switch. --- src/search/planner.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/search/planner.cc b/src/search/planner.cc index 5a5d6ca79d..739bf999c4 100644 --- a/src/search/planner.cc +++ b/src/search/planner.cc @@ -56,15 +56,20 @@ int main(int argc, const char **argv) { SearchStatus search_status = search_algorithm->get_status(); ExitCode exitcode; - if (search_status == SearchStatus::SOLVED) { + switch (search_status) { + case SearchStatus::SOLVED: exitcode = ExitCode::SUCCESS; - } else if (search_status == SearchStatus::TIMEOUT) { + break; + case SearchStatus::TIMEOUT: exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; - } else if (search_status == SearchStatus::UNSOLVABLE) { + break; + case SearchStatus::UNSOLVABLE: exitcode = ExitCode::SEARCH_UNSOLVABLE; - } else if (search_status == SearchStatus::UNSOLVABLE_WITHIN_BOUND) { + break; + case SearchStatus::UNSOLVABLE_WITHIN_BOUND: exitcode = ExitCode::SEARCH_UNSOLVABLE_WITHIN_BOUND; - } else { + break; + default: assert(search_status == SearchStatus::FAILED); exitcode = ExitCode::SEARCH_UNSOLVED_INCOMPLETE; } From 4bf5f9c673c6d99a1498c344943e454fe99e48db Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 20 Feb 2026 11:18:54 +0100 Subject: [PATCH 27/29] Add exitcode tests. --- .../benchmarks/blocks-unsolvable/domain.pddl | 48 +++++++++++++++++++ .../blocks-unsolvable/probBLOCKS-4-0.pddl | 7 +++ misc/tests/test-exitcodes.py | 11 +++++ 3 files changed, 66 insertions(+) create mode 100644 misc/tests/benchmarks/blocks-unsolvable/domain.pddl create mode 100644 misc/tests/benchmarks/blocks-unsolvable/probBLOCKS-4-0.pddl diff --git a/misc/tests/benchmarks/blocks-unsolvable/domain.pddl b/misc/tests/benchmarks/blocks-unsolvable/domain.pddl new file mode 100644 index 0000000000..f6cadcda32 --- /dev/null +++ b/misc/tests/benchmarks/blocks-unsolvable/domain.pddl @@ -0,0 +1,48 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; 4 Op-blocks world +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define (domain BLOCKS) + (:requirements :strips) + (:predicates (on ?x ?y) + (ontable ?x) + (clear ?x) + (handempty) + (holding ?x) + ) + + (:action pick-up + :parameters (?x) + :precondition (and (clear ?x) (ontable ?x) (handempty)) + :effect + (and (not (ontable ?x)) + (not (clear ?x)) + (not (handempty)) + (holding ?x))) + + (:action put-down + :parameters (?x) + :precondition (holding ?x) + :effect + (and (not (holding ?x)) + (clear ?x) + (handempty) + (ontable ?x))) + (:action stack + :parameters (?x ?y) + :precondition (and (holding ?x) (clear ?y)) + :effect + (and (not (holding ?x)) + (not (clear ?y)) + (clear ?x) + (handempty) + (on ?x ?y))) + (:action unstack + :parameters (?x ?y) + :precondition (and (on ?x ?y) (clear ?x) (handempty)) + :effect + (and (holding ?x) + (clear ?y) + (not (clear ?x)) + (not (handempty)) + (not (on ?x ?y))))) diff --git a/misc/tests/benchmarks/blocks-unsolvable/probBLOCKS-4-0.pddl b/misc/tests/benchmarks/blocks-unsolvable/probBLOCKS-4-0.pddl new file mode 100644 index 0000000000..1119ef2be9 --- /dev/null +++ b/misc/tests/benchmarks/blocks-unsolvable/probBLOCKS-4-0.pddl @@ -0,0 +1,7 @@ +(define (problem BLOCKS-4-0) +(:domain BLOCKS) +(:objects D B A C ) +(:INIT (CLEAR C) (CLEAR A) (CLEAR B) (CLEAR D) (ONTABLE C) (ONTABLE A) + (ONTABLE B) (ONTABLE D) (HANDEMPTY)) +(:goal (AND (ON D C) (ON C B) (ON B A) (ON A D))) +) diff --git a/misc/tests/test-exitcodes.py b/misc/tests/test-exitcodes.py index 21691cb3d8..a353ebe037 100644 --- a/misc/tests/test-exitcodes.py +++ b/misc/tests/test-exitcodes.py @@ -38,6 +38,7 @@ "strips": "miconic/s1-0.pddl", "axioms": "philosophers/p01-phil2.pddl", "cond-eff": "miconic-simpleadl/s1-0.pddl", + "unsolvable": "blocks-unsolvable/probBLOCKS-4-0.pddl", "large": "satellite/p25-HC-pfile5.pddl", } @@ -121,6 +122,16 @@ defaultdict(lambda: returncodes.SEARCH_UNSUPPORTED)), ("cond-eff", [], MERGE_AND_SHRINK, defaultdict(lambda: returncodes.SUCCESS)), + ("unsolvable", [], "astar(blind())", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "lazy(single(lmcut()))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "eager(alt([single(const(infinity)),single(const(1))]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "ehc(blind())", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "eager(single(blind(),pref_only=true))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), # We cannot set/enforce memory limits on Windows/macOS and thus expect # DRIVER_UNSUPPORTED as exit code in those cases. ("large", ["--search-memory-limit", "100M"], MERGE_AND_SHRINK, From 65f1b2201fb0ab7166a754ad578b5ca1e3da0754 Mon Sep 17 00:00:00 2001 From: Claudia Grundke Date: Fri, 20 Feb 2026 15:30:08 +0100 Subject: [PATCH 28/29] Add more exit code tests and a missing driver return code. --- driver/returncodes.py | 1 + misc/tests/test-exitcodes.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/driver/returncodes.py b/driver/returncodes.py index 3c544abd73..6c8f0325ce 100644 --- a/driver/returncodes.py +++ b/driver/returncodes.py @@ -14,6 +14,7 @@ TRANSLATE_UNSOLVABLE = 10 SEARCH_UNSOLVABLE = 11 SEARCH_UNSOLVED_INCOMPLETE = 12 +SEARCH_UNSOLVABLE_WITHIN_BOUND = 13 TRANSLATE_OUT_OF_MEMORY = 20 TRANSLATE_OUT_OF_TIME = 21 diff --git a/misc/tests/test-exitcodes.py b/misc/tests/test-exitcodes.py index a353ebe037..5db736f09f 100644 --- a/misc/tests/test-exitcodes.py +++ b/misc/tests/test-exitcodes.py @@ -124,14 +124,34 @@ defaultdict(lambda: returncodes.SUCCESS)), ("unsolvable", [], "astar(blind())", defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "astar(lmcut())", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), ("unsolvable", [], "lazy(single(lmcut()))", defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), ("unsolvable", [], "eager(alt([single(const(infinity)),single(const(1))]))", defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "eager(type_based([const(infinity),const(1)]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "eager(pareto([const(infinity),const(1)]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "eager(tiebreaking([const(infinity),const(1)],unsafe_pruning=false))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE)), + ("unsolvable", [], "astar(blind(),bound=2)", + defaultdict(lambda: returncodes.SEARCH_UNSOLVABLE_WITHIN_BOUND)), ("unsolvable", [], "ehc(blind())", defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), ("unsolvable", [], "eager(single(blind(),pref_only=true))", defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "eager(pareto([blind()],pref_only=true))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "eager(pareto([cg(),cea()]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "eager(tiebreaking([const(infinity),blind()]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "eager(alt([single(cg()),single(const(infinity))]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), + ("unsolvable", [], "lazy(type_based([cg(),const(infinity)]))", + defaultdict(lambda: returncodes.SEARCH_UNSOLVED_INCOMPLETE)), # We cannot set/enforce memory limits on Windows/macOS and thus expect # DRIVER_UNSUPPORTED as exit code in those cases. ("large", ["--search-memory-limit", "100M"], MERGE_AND_SHRINK, From 04e80a7cd7a056740fe18ee7b06e2facc6364fa6 Mon Sep 17 00:00:00 2001 From: Simon Dold Date: Wed, 25 Feb 2026 15:15:22 +0100 Subject: [PATCH 29/29] update report message. --- src/search/search_algorithm.cc | 4 ++++ src/search/search_algorithms/eager_search.cc | 1 - src/search/search_algorithms/enforced_hill_climbing_search.cc | 3 +-- src/search/search_algorithms/lazy_search.cc | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/search/search_algorithm.cc b/src/search/search_algorithm.cc index 7065befd7d..d92d56814c 100644 --- a/src/search/search_algorithm.cc +++ b/src/search/search_algorithm.cc @@ -103,10 +103,14 @@ SearchStatus SearchAlgorithm::get_finished_search_status() const { if (found_solution()) { return SOLVED; } else if (is_unbounded() && is_complete_within_bound()) { + log << "Search terminated -- no plan exists!" << endl; return UNSOLVABLE; } else if (is_complete_within_bound()) { + log << "Search terminated -- no plan with cost " << bound - 1 + << " or less exists!" << endl; return UNSOLVABLE_WITHIN_BOUND; } else { + log << "Search terminated without finding a plan!" << endl; return FAILED; } } diff --git a/src/search/search_algorithms/eager_search.cc b/src/search/search_algorithms/eager_search.cc index 79c40bf745..d8cc299cdf 100644 --- a/src/search/search_algorithms/eager_search.cc +++ b/src/search/search_algorithms/eager_search.cc @@ -117,7 +117,6 @@ SearchStatus EagerSearch::step() { optional node = get_next_node_to_expand(); if (!node.has_value()) { assert(open_list->empty()); - log << "Completely explored state space -- no solution!" << endl; return get_finished_search_status(); } return expand(node.value()); diff --git a/src/search/search_algorithms/enforced_hill_climbing_search.cc b/src/search/search_algorithms/enforced_hill_climbing_search.cc index c302324d42..a02667c15e 100644 --- a/src/search/search_algorithms/enforced_hill_climbing_search.cc +++ b/src/search/search_algorithms/enforced_hill_climbing_search.cc @@ -235,8 +235,7 @@ SearchStatus EnforcedHillClimbingSearch::ehc() { } } } - log << "No solution - FAILED" << endl; - return FAILED; + return get_finished_search_status(); } void EnforcedHillClimbingSearch::print_statistics() const { diff --git a/src/search/search_algorithms/lazy_search.cc b/src/search/search_algorithms/lazy_search.cc index db8480bdde..c4c6f921ae 100644 --- a/src/search/search_algorithms/lazy_search.cc +++ b/src/search/search_algorithms/lazy_search.cc @@ -121,7 +121,6 @@ void LazySearch::generate_successors() { SearchStatus LazySearch::fetch_next_state() { if (open_list->empty()) { - log << "Completely explored state space -- no solution!" << endl; return get_finished_search_status(); }