The Aleph state-space search framework provides five engine families. Each one targets a different problem shape. This guide helps you pick the right engine for your domain and explains the trade-offs between them.
Is the search adversarial (two-player, zero-sum)?
│
├─ YES
│ │
│ └─ Can you write an evaluate(state) from the side-to-move perspective?
│ │
│ ├─ YES, and I need pruning (large game trees)
│ │ └─ ▶ Alpha-Beta (optionally with TT, iterative deepening,
│ │ aspiration windows, move ordering)
│ │
│ └─ YES, and the tree is small or I want a baseline
│ └─ ▶ Negamax (optionally with TT)
│
└─ NO (single-agent optimization or enumeration)
│
├─ Do I need to optimize an objective (min or max)?
│ │
│ ├─ YES, and I can compute an optimistic bound
│ │ └─ ▶ Branch and Bound (depth-first or best-first)
│ │
│ └─ NO, I just need feasible solutions or to enumerate them
│ │
│ ├─ Is the cost function monotone and do I want optimal cost?
│ │ └─ ▶ IDA\* (with admissible heuristic)
│ │
│ └─ I want all solutions, or the first feasible one
│ └─ ▶ DFS / Backtracking
| Feature | DFS / Backtracking | Branch & Bound | IDA* | Negamax | Alpha-Beta |
|---|---|---|---|---|---|
| Problem type | Enumeration / feasibility | Optimization | Optimal-cost path | Adversarial (baseline) | Adversarial (pruning) |
| Players | 1 | 1 | 1 | 2 | 2 |
| Pruning | Domain-only | Bound vs incumbent | Threshold | None | Alpha-beta window |
| Memory | O(depth) | O(depth) or O(frontier) | O(depth) | O(depth) | O(depth) |
Requires bound() |
No | Yes | No | No | No |
Requires heuristic() |
No | No | Yes | No | No |
Requires evaluate() |
No | No | No | Yes | Yes |
| Optimal guarantee | Exhaustive | Yes (with admissible bound) | Yes (with admissible heuristic) | Yes (full depth) | Yes (full depth) |
| TT support | Via visited-set | Planned | No | Yes | Yes |
| Move ordering | No | Estimated_Bound | No | No | Estimated_Score, killer, history |
| Iterative deepening | No | No | Built-in | Via wrapper | Via wrapper |
Use when you need to find one feasible solution, enumerate all solutions, or explore an implicit space without optimization.
Domain contract: is_goal(), for_each_successor(), apply(), undo().
Typical problems:
- N-Queens placement
- Sudoku solving
- Graph coloring (feasibility)
- Constraint satisfaction without optimization
Key settings:
stop_at_first_solution = truefor early exitmax_depthto limit exploration depthmax_solutionsto cap enumeration- Pass a
SearchSolutionCollectorto collect multiple solutions
Example: backtracking_nqueens_example.cc, backtracking_subset_sum_example.cc
Use when you are solving a combinatorial optimization problem and can provide an optimistic bound on partial solutions.
Domain contract: everything from DFS plus is_complete(),
objective_value(), bound().
Typical problems:
- 0/1 Knapsack
- Assignment / scheduling
- Traveling salesman (with lower-bound relaxation)
- Any problem where partial states admit a cheap optimistic estimate
Key settings:
Maximize_Objective<T>orMinimize_Objective<T>to set directionBest_Firststrategy for best-bound-first exploration (uses a heap)Depth_FirstwithMoveOrderingMode::Estimated_Boundfor bound-ordered DFSmax_expansionsas a hard budget
Why not DFS? DFS finds solutions but has no mechanism to prune suboptimal branches. Branch and bound tracks an incumbent and uses the bound to skip entire subtrees that cannot improve it.
Why not IDA*? IDA* targets shortest-path-like problems with monotone costs. Branch and bound handles arbitrary objective functions (maximize or minimize) and non-monotone bounds.
Example: branch_and_bound_knapsack_example.cc, branch_and_bound_assignment_example.cc
Use when you want the optimal-cost solution in a single-agent space and have an admissible heuristic, but the state space is too large for explicit storage of the frontier (as BFS/A* would require).
Domain contract: is_goal(), for_each_successor(), apply(), undo(),
plus heuristic(state) -> Cost and each move carries a Cost cost field.
Typical problems:
- Sliding-tile puzzles (15-puzzle, 24-puzzle)
- Shortest path in implicit graphs with uniform or weighted edges
- Planning problems with admissible heuristics
Key settings:
- The engine manages the cost threshold internally
max_depthlimits ply depth per iterationmax_expansionsas a global budget across all iterations
Why not DFS? DFS finds a solution, not necessarily the cheapest one. IDA* guarantees cost-optimality when the heuristic is admissible.
Why not Branch and Bound? For shortest-path problems with monotone edge
costs, IDA* is simpler (no bound() needed, no incumbent management) and
uses less memory than best-first B&B.
Example: The test suite in state_search_framework_test.cc contains IDA* examples with grid-based domains.
Use when you are modeling a two-player, zero-sum, alternating-turn game and want a correct baseline engine.
Domain contract: is_terminal(), evaluate() (from the perspective of
the side to move), for_each_successor(), apply(), undo().
Typical problems:
- Small complete games (Tic-Tac-Toe)
- Reference implementation to validate Alpha-Beta results
- Games where the tree is small enough that pruning is unnecessary
Key settings:
max_depthsets the search horizon in plies- Attach a
Transposition_Tableto avoid re-evaluating the same position
Why not Alpha-Beta? Negamax explores the full minimax tree. Use it when the tree is small enough that you don't need pruning, or as a reference to verify that Alpha-Beta returns the same value.
Example: negamax_simple_example.cc
Use when you need adversarial search on a game tree that is too large for full Negamax enumeration.
Domain contract: same as Negamax.
Typical problems:
- Chess, checkers, Connect-4 (with depth-limited search)
- Any game where good move ordering can prune large portions of the tree
Key settings:
MoveOrderingMode::Estimated_Score— sort children by-evaluate(child)before exploring them (dramatically improves pruning)use_killer_moves = true— promote recent cutoff-producing movesuse_history_heuristic = true— promote historically successful moves (requiresmove_key()on the domain)- Iterative deepening with
iterative_deepening_alpha_beta_search() - Aspiration windows via
AdversarialIterativeDeepeningOptions - Transposition tables for memoization across the search
Why not Negamax? Alpha-Beta returns the same value as Negamax but visits far fewer nodes. The reduction depends on move ordering quality — with perfect ordering, Alpha-Beta examines O(b^(d/2)) nodes instead of O(b^d).
Example: negamax_tictactoe_example.cc (advanced), alpha_beta_connect3_example.cc
A natural progression for adversarial search:
- Negamax — verify domain contract, check game-theoretic value
- Alpha-Beta — add pruning, confirm same value with fewer nodes
- + Move ordering — enable
Estimated_Score, measure cutoff improvement - + Transposition table — reduce redundant evaluations
- + Iterative deepening — get anytime behavior and PV at each depth
- + Aspiration windows — narrow the root window for faster convergence
For optimization:
- DFS / Backtracking — enumerate solutions, verify domain contract
- Branch and Bound — add
bound()and objective, enable pruning - + Best-first — switch strategy for tighter frontier management
- + Bound ordering — rank DFS children by bound for earlier incumbents
- Negamax vs Alpha-Beta: both must return the same root value for the same position and depth. If they disagree, the domain contract has a bug.
- PV replay: apply the principal variation to the root state and verify
that
evaluate(terminal)is consistent with the reported value. - Statistics cross-check: Alpha-Beta
visited_statesmust be ≤ Negamaxvisited_statesfor the same position and depth.
| I want to... | Use |
|---|---|
| Find any feasible solution | DFS / Backtracking |
| Enumerate all solutions | DFS / Backtracking (stop_at_first_solution = false) |
| Find the cheapest path | IDA* |
| Maximize or minimize an objective | Branch and Bound |
| Solve a two-player game (small) | Negamax |
| Solve a two-player game (large) | Alpha-Beta + move ordering + TT |