Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Backtracking.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
47# ifndef BACKTRACKING_H
48# define BACKTRACKING_H
49
50#include <utility>
51
52#include <ah-errors.H>
53#include <state_search_common.H>
54
55namespace Aleph {
56
57namespace state_search_detail {
58
63template <SearchState State, SearchMove Move>
65{
67 const SearchSolution<State, Move> &rhs) const noexcept
68 {
69 return lhs.depth < rhs.depth;
70 }
71};
72
73} // end namespace state_search_detail
74
84template <BacktrackingDomain Domain>
86{
87public:
91 using State = typename Domain::State;
93 using Move = typename Domain::Move;
100
108 static constexpr bool supports_best_first = false;
109
129 const SearchLimits &limits = {},
130 Solution_Compare compare = {})
131 : domain_(std::move(domain)), policy_(policy), limits_(limits), compare_(std::move(compare))
132 {
133 // empty
134 }
135
145 {
146 return domain_;
147 }
148
158 {
159 return domain_;
160 }
161
171 {
172 return policy_;
173 }
174
184 {
185 return limits_;
186 }
187
196 void set_policy(const ExplorationPolicy &policy) noexcept
197 {
198 policy_ = policy;
199 }
200
209 void set_limits(const SearchLimits &limits) noexcept
210 {
211 limits_ = limits;
212 }
213
236
253 template <typename OnSolution>
256 {
258 << "Depth_First_Backtracking only supports depth-first strategy";
260 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
261
262 Result result(compare_);
263 result.policy = policy_;
264 result.limits = limits_;
265 const auto start_time = SearchClock::now();
266
267 SearchPath<Move> path;
269 (void) dfs(initial_state, path, 0, result, on_solution);
270
271 if (result.status == SearchStatus::NotStarted)
273
274 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
275
276 return result;
277 }
278
289 template <typename OnSolution>
292 {
293 auto handler = std::forward<OnSolution>(on_solution);
294 return search(std::move(initial_state), handler);
295 }
296
320 template <typename VisitedSet>
324 {
326 return search(std::move(initial_state), visited, on_solution);
327 }
328
348 template <typename VisitedSet, typename OnSolution>
353 {
355 << "Depth_First_Backtracking only supports depth-first strategy";
357 << "SearchLimits::max_solutions must be positive or Search_Unlimited";
358
359 Result result(compare_);
360 result.policy = policy_;
361 result.limits = limits_;
362 const auto start_time = SearchClock::now();
363
364 SearchPath<Move> path;
366 (void) dfs_visited(initial_state, path, 0, result, on_solution, visited);
367
368 if (result.status == SearchStatus::NotStarted)
370
371 result.stats.elapsed_ms = search_elapsed_ms(SearchClock::now() - start_time);
372
373 return result;
374 }
375
376private:
381
382 [[nodiscard]] bool stop_after_solution(Result &result) const
383 {
385 }
386
388 {
390 }
391
392 template <typename OnSolution>
394 [[nodiscard]] bool dfs(State &state,
395 SearchPath<Move> &path,
396 const size_t depth,
397 Result &result,
399 {
400 ++result.stats.visited_states;
401 if (depth > result.stats.max_depth_reached)
402 result.stats.max_depth_reached = depth;
403
404 if (domain_.is_goal(state))
405 {
406 ++result.stats.solutions_found;
407 Solution solution{state, path, depth};
408 result.best_solution.consider(solution);
409
410 if (not on_solution(solution))
411 {
413 return true;
414 }
415
416 return stop_after_solution(result);
417 }
418
420 {
421 ++result.stats.terminal_states;
422 return false;
423 }
424
425 if (depth >= limits_.max_depth)
426 {
427 ++result.stats.pruned_by_depth;
428 return false;
429 }
430
432 {
433 ++result.stats.pruned_by_domain;
434 return false;
435 }
436
437 if (expansion_limit_reached(result))
438 return true;
439
440 ++result.stats.expanded_states;
441
442 bool stop = false;
443 (void) domain_.for_each_successor(state,
444 [&](const Move &move) -> bool
445 {
447 path.append(move);
448 domain_.apply(state, move);
449 try
450 {
451 stop = dfs(state, path, depth + 1, result, on_solution);
452 }
453 catch (...)
454 {
455 domain_.undo(state, move);
456 (void) path.remove_last();
457 throw;
458 }
459 domain_.undo(state, move);
460 (void) path.remove_last();
461 return not stop;
462 });
463
464 return stop;
465 }
466
467 template <typename OnSolution, typename VisitedSet>
470 [[nodiscard]] bool dfs_visited(State &state,
471 SearchPath<Move> &path,
472 const size_t depth,
473 Result &result,
475 VisitedSet &visited)
476 {
477 ++result.stats.visited_states;
478 if (depth > result.stats.max_depth_reached)
479 result.stats.max_depth_reached = depth;
480
481 const auto key = domain_.state_key(state);
482 if (auto *entry = visited.search(key);
483 entry != nullptr and entry->second <= depth)
484 {
485 ++result.stats.pruned_by_visited;
486 return false;
487 }
488
489 if (domain_.is_goal(state))
490 {
491 ++result.stats.solutions_found;
492 Solution solution{state, path, depth};
493 result.best_solution.consider(solution);
494
495 if (not on_solution(solution))
496 {
498 return true;
499 }
500
501 return stop_after_solution(result);
502 }
503
505 {
506 ++result.stats.terminal_states;
507 return false;
508 }
509
510 if (depth >= limits_.max_depth)
511 {
512 ++result.stats.pruned_by_depth;
513 return false;
514 }
515
517 {
518 ++result.stats.pruned_by_domain;
519 return false;
520 }
521
522 if (expansion_limit_reached(result))
523 return true;
524
525 if (auto *entry = visited.search(key); entry != nullptr)
526 entry->second = depth;
527 else
528 visited.insert(key, depth);
529
530 ++result.stats.expanded_states;
531
532 bool stop = false;
533 (void) domain_.for_each_successor(state,
534 [&](const Move &move) -> bool
535 {
537 path.append(move);
538 domain_.apply(state, move);
539 try
540 {
541 stop = dfs_visited(state, path, depth + 1, result, on_solution, visited);
542 }
543 catch (...)
544 {
545 domain_.undo(state, move);
546 (void) path.remove_last();
547 throw;
548 }
549 domain_.undo(state, move);
550 (void) path.remove_last();
551 return not stop;
552 });
553
554 return stop;
555 }
556};
557
559template <BacktrackingDomain Domain>
561 typename Domain::State initial_state,
562 ExplorationPolicy policy = {},
563 SearchLimits limits = {})
564{
565 Depth_First_Backtracking<Domain> engine(std::move(domain), policy, limits);
566 return engine.search(std::move(initial_state));
567}
568
570template <BacktrackingDomain Domain, typename OnSolution>
573 typename Domain::State initial_state,
575 ExplorationPolicy policy = {},
576 SearchLimits limits = {})
577{
578 Depth_First_Backtracking<Domain> engine(std::move(domain), policy, limits);
579 return engine.search(std::move(initial_state), on_solution);
580}
581
582} // end namespace Aleph
583
584# endif // BACKTRACKING_H
Exception handling system with formatted messages for Aleph-w.
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
bool consider(const Solution &candidate)
Consider a candidate by copy.
Recursive depth-first backtracking over an implicit state space.
typename Domain::Move Move
Move type.
Depth_First_Backtracking(Domain domain, ExplorationPolicy policy={}, const SearchLimits &limits={}, Solution_Compare compare={})
Build an engine bound to one domain adapter.
bool expansion_limit_reached(Result &result) const
void set_limits(const SearchLimits &limits) noexcept
Replace the hard limits for future runs.
and VisitedBoundMap< VisitedSet, typename Domain::State_Key, size_t > Result search(State initial_state, VisitedSet &visited)
DFS/backtracking with a depth-aware visited-state map.
const SearchLimits & limits() const noexcept
Current hard limits.
Result search(State initial_state, OnSolution &&on_solution)
Rvalue-friendly overload for temporary solution visitors.
static constexpr bool supports_best_first
Compile-time marker: Depth_First_Backtracking only supports Depth_First strategy.
state_search_detail::PreferShallowerSolution< State, Move > Solution_Compare
Internal solution comparator.
bool stop_after_solution(Result &result) const
typename Domain::State State
Concrete search state type.
const ExplorationPolicy & policy() const noexcept
Current exploration policy.
Domain Domain_Type
Type of the problem domain.
bool dfs(State &state, SearchPath< Move > &path, const size_t depth, Result &result, OnSolution &on_solution)
void set_policy(const ExplorationPolicy &policy) noexcept
Replace the exploration policy for future runs.
Domain & domain() noexcept
Mutable access to the bound domain adapter.
and VisitedBoundMap< VisitedSet, typename Domain::State_Key, size_t > bool dfs_visited(State &state, SearchPath< Move > &path, const size_t depth, Result &result, OnSolution &on_solution, VisitedSet &visited)
Result search(State initial_state, OnSolution &on_solution)
Execute DFS/backtracking and invoke on_solution on each solution.
Result search(State initial_state)
Execute a depth-first backtracking search from initial_state.
and VisitedBoundMap< VisitedSet, typename Domain::State_Key, size_t > and SearchSolutionVisitor< OnSolution, Solution > Result search(State initial_state, VisitedSet &visited, OnSolution &on_solution)
DFS/backtracking with depth-aware visited-state tracking and a solution callback.
const Domain & domain() const noexcept
Read-only access to the bound domain adapter.
Concept for callbacks invoked on each accepted solution.
Generic concept for domains that can expose a stable state key.
Concept for a map tracking the best bound seen per visited state.
bool should_prune_state(Domain &domain, const typename Domain::State &state, const size_t depth)
Dispatch helper for the optional should_prune hook.
bool is_terminal_state(const Domain &domain, const typename Domain::State &state)
Dispatch helper for the optional is_terminal hook.
bool stop_after_solution(Result &result, const ExplorationPolicy &policy, const SearchLimits &limits)
Decide whether to stop the search after a solution was accepted.
bool expansion_limit_reached(Result &result, const SearchLimits &limits)
Check whether the expansion limit has been reached.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
@ Exhausted
Search space within the configured bounds was exhausted.
@ StoppedOnSolution
Search stopped because solution handling requested it.
@ NotStarted
Search object exists but no traversal has run yet.
void reserve_search_path(SearchPath< Move > &path, const SearchLimits &limits)
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
double search_elapsed_ms(const SearchClock::duration &duration) noexcept
@ Domain
Preserve the order emitted by for_each_successor().
auto backtracking_search(Domain domain, typename Domain::State initial_state, ExplorationPolicy policy={}, SearchLimits limits={})
Convenience wrapper that runs a one-shot DFS/backtracking search.
STL namespace.
Common infrastructure for implicit state-space search.
Default solution visitor that always continues the search.
Exploration controls shared across engines.
Strategy strategy
Traversal strategy.
@ Depth_First
Recursive depth-first traversal (all engines).
Hard bounds applied by the search engine.
size_t max_solutions
Maximum accepted solutions.
size_t max_depth
Maximum expansion depth.
Aggregates the outcome of one search execution.
SearchStats stats
Statistics collected during the run.
ExplorationPolicy policy
Exploration policy used for the run.
SearchLimits limits
Limits used for the run.
SearchStatus status
Final execution state.
Incumbent_Type best_solution
Best incumbent retained by the engine.
Snapshot of a concrete solution encountered during the traversal.
size_t pruned_by_visited
States skipped because already seen (visited-set duplicate suppression).
size_t pruned_by_domain
States discarded by domain-side pruning.
size_t terminal_states
Non-solution terminal states cut by the domain.
double elapsed_ms
Wall-clock time spent inside the search call.
size_t pruned_by_depth
States not expanded due to max depth.
size_t generated_successors
Number of successor moves emitted.
size_t max_depth_reached
Deepest path depth visited.
size_t solutions_found
Number of goal states accepted.
size_t expanded_states
Number of non-terminal states expanded.
size_t visited_states
Number of states entered by the engine.
Default incumbent ordering for DFS solutions.
bool operator()(const SearchSolution< State, Move > &lhs, const SearchSolution< State, Move > &rhs) const noexcept
static mt19937 engine