400 likes | 406 Views
Programming Search. Overview. Preliminary: eliminating general disjunctions Classification of search methods Heuristic tree search Incomplete tree search Optimization. Search. Search is the default mechanism to deal with disjunctions
E N D
Overview • Preliminary: eliminating general disjunctions • Classification of search methods • Heuristic tree search • Incomplete tree search • Optimization
Search • Search is the default mechanism to deal with disjunctions • The simplest form of disjunction is a domain declaration: X::1..3 • Other disjunctions: • neighbour(X,Y) :- X #= Y+1. • neighbour(X,Y) :- X #= Y-1. • It would be nice to have all disjunctions in the form of domain variables: more uniform and flexible!
Eliminating general disjunctions • Eliminate choice points from constraint model • Introduce decision variables for choices (often Boolean) • Defer choice-making until search phase • General delay method in ECLiPSe • neighbour(X,Y,0) :- X #= Y+1. • neighbour(X,Y,1) :- X #= Y-1. • ?- suspend(neighbour(X,Y,B),3,B->inst). • Arithmetic method • neighbour(X,Y,B) :- • X #>= Y-1, X #=< Y+1, • B :: 0..1, • X #>= Y-1 + 2*B, X #=< Y-1 + 2*B. • Now all disjunctions correspond to domain variables!
Search Spaces • We assume • Problem formulated in terms of variables and constraints • Variables have domains (capturing the choices) • An assignment assigns values (from the domain) to variables • Total assignment: every variable has a (fixed) value • A solution is a total assignment that satisfies all constraints • The search space is the set of all possible total assignments
solution no solution Exploring Search Spaces • Search Methods: • Complete vs Incomplete • Constructive vs Move-based • Random vs Systematic
Exploring search spaces partial assignments • Tree search: • constructive (partial/total assignments) • systematic • complete or incomplete • “Local” search: • move-based (only total assignments) • random or systematic • incomplete
X Y Z Search tree • All disjunctions correspond to domain variables • Search tree: Some of these leaf nodes are our solutions! • We restrict ourselves to depth-first search • Depth-first search is done by backtracking • limited flexibility in visiting the nodes • but the only(?) memory-efficient general complete search method
2 4 3 1 Backtracking: Degrees of Freedom X::1..2, Y::1..4 • Variable selection X Y Y X X,Y = 1,1 1,2 1,3 1,4 2,1 2,2 2,3 2,4 1,1 2,1 1,2 2,2 1,3 2,3 1,4 2,4 • Value selection 1 2 3 4
Basic search code (built-in) labeling(AllVars) :- ( fromto(AllVars, [X|RestVars], RestVars, []) do indomain(X) % choice here ). indomain(X) :- get_ic_domain_as_list(X, Values), member(Value, Values), X = Value.
Advanced search code (template) labeling(AllVars) :- static_preorder(AllVars, OrderedVars), ( fromto(OrderedVars, Vars, RestVars, []) do select_variable(X, Vars, RestVars), get_domain_as_list(X, Values), select_value(Value, Values), % choice here X = Value ).
Example: N-Queens Y Our model: queens(N, Board) :- length(Board, N), Board :: 1..N, ( fromto(Board, [Q1|Cols], Cols, []) do ( foreach(Q2, Cols), count(Dist,1,_), param(Q1) do noattack(Q1, Q2, Dist) ) ). noattack(Q1,Q2,Dist) :- Q2 #\= Q1, Q2 - Q1 #\= Dist, Q1 - Q2 #\= Dist. Y Y Y Y Y Y Y
N-Queens: Propagation and search • Constraint Propagation • eliminates choices • e.g. instantiates 6th queen • Naive search • Alright for 8 queens • 0.01s first solution • 0.2s all 92 solutions Y Y Y
Forward Checking 32-Queens Naive search > 100 secs despite propagation...
Improving Search using Heuristics General-purpose heuristic: “first-fail” • Focus on Bottleneck • Label variable with smallest domain first • Forward-checking enhances first-fail • Quite general, works almost always Queens-specific heuristic • Start in the middle
Improving search for Queens :- lib(ic_search). labeling_a(AllVars) :- ( fromto(AllVars, Vars, RestVars, []) do Vars = [Var|RestVars], indomain(Var) ). labeling_b(AllVars) :- ( fromto(AllVars, Vars, RestVars, []) do delete(Var, Vars, RestVars, 0, first_fail), indomain(Var) ).
Improving search for Queens labeling_d(AllVars) :- middle_first(AllVars, AllVarsPreOrdered), ( fromto(AllVarsPreOrdered, Vars, VarsRem, []) do delete(Var, Vars, VarsRem, 0, first_fail), indomain(Var) ). labeling_e(AllVars) :- middle_first(AllVars, AllVarsPreOrdered), ( fromto(AllVarsPreOrdered, Vars, VarsRem, []) do delete(Var, Vars, VarsRem, 0, first_fail), indomain(Var, middle) ).
N-Queens - Comparison of Heuristics Number of backtracks N = 8 12 14 16 32 64 128 256 labeling_a 10 15 103 542 - - - - labeling_b 10 16 11 3 4 148 - - labeling_c 0 3 22 17 - - - - labeling_d 0 0 1 0 1 1 - - labeling_e 3 3 38 3 7 1 0 0 - = timeout
Variable selection primitives • Vars = [Var|VarsRest] • delete(Var, Vars, VarsRest, 0, input_order) • select first variable in the list. • delete(Var, Vars, VarsRest, 0, first_fail) • select variable with smallest domain. • delete(Var, Vars, VarsRest, most_constrained) • select variable with smallest domain which is involved in the largest number of constraints. • delete(Var, Vars, VarsRest, 0, smallest) • select the variable which has the smallest value in its domain. Useful for scheduling.
Value selection ... • Binary choice • X=0 ; X=1 • Enumerate • X=1 ; X=2 ; X=3 ; X=4 • indomain(X) • indomain(X, Order) Order is one of: min, max, middle, split, random, …
... and other choices • Any disjunction that partitions the search space: • split_domain(X) :- • get_ic_bounds(X, Low, High),Split is (High+Low)//2, • ( X #<= Split ; X #> Split ). • A la MIP: • ( X #<= floor(LpSol) ; X #>= ceiling(LpSol)). • Heuristics: • ( X = SuggestedX ; X #\= SuggestedX ). • Caution: eventually all variables need to be instantiated!
Why is search strategy so important? • Early decomposition into subproblems • if problem is exponential, solving and recombining subproblems is cheaper than solving the whole • Breaking cycles in the constraint network • cycles make constraint propagation expensive • Benefits in optimization context • good early cost bounds cut the search
Decomposition Suppose each variable has two possible values. C B A Depth-first search over 9 nodes: #steps = 29 = 512 D E F Using Decomposition: G Label E, then solve {A,B,C,D} and {F,G,H,J} independently: #steps = 2*(24 + 24) = 64 H J
Decomposition (sub-problems) C B A C B A 0 D 1 D 0 1 F F G G H H J J #steps = 2*(24 + 24) = 64
Cycle-Free Problem B A C Suppose each variable has two possible values. E Depth-first search over 6 nodes: #steps = 26 = 64 F D Polynomial search algorithm: Independently handle {D,A,B}, {C,B} and {F,E,B} E.G. {D,A,B}: Find all solutions to {A,D} Reduce domain of A Find all solutions to {A,B} Reduce domain of B If domain of B non-empty, succeed! Cost of polynomial algorithm: #steps = 5*(Cost of solving binary prob) = 30
A B C D E G F Cutting Cycles Label D, then use polynomial algorithm to label the remainder. Suppose each variable has two possible values. Depth-first search over 7 nodes: #steps = 27 = 128 Cost of polynomial algorithm: #steps = 2*(5*(Cost of binary prob) + 3*(Cost of unary prob)) = 72 A B C 0/1 0/1 0/1 E G F
Incomplete Tree Search • Tree search is normally complete • needs only a stack for systematic, complete traversal • tree can be reshaped by variable and value selection • but possible traversal orders are restricted • Incompleteness by ignoring sub-trees, e.g. • first solution only • time-out • bounded backtracks • limited credit • limited discrepancy
Predefined incomplete strategies (1) search/6 in lib(ic_search) and lib(fd_search) Bounded-backtrack search: Depth-bounded, then bounded-backtrack search:
Predefined incomplete strategies (2) Credit-based search: Limited Discrepancy Search:
Template for Limited Discrepancy Search lds(AllVars,Discrepancies) :- ( fromto(Vars, Vars, RestVars, []), fromto(Discrepancies, Disc, DiscRem, _) do select_variable(X, Vars, RestVars), once(select_value(X, Value)), ( X = Value, DiscRem = Disc ; Disc > 0, X #\= Value, DiscRem is Disc-1, indomain(X) ) ).
Predefined Search Routine • search/6 in lib(ic_search) • search(List, VarIndex, VarSelect, ValSelect, Method, Options) • List of variables (or structures containing variables) • 0 (or variable index within the structures) • Variable selection strategy: input_order, first_fail, … • Value selection strategy: indomain, indomain_middle, split, … • Tree search method: complete, bbs, credit, dbs, lds, … • Options, e.g. counting backtracks
First Solution, All Solutions first(Vars) :- setup_constraints(Vars), once labeling(Vars). all(AllSolutions) :- setup_constraints(Vars), findall(Vars, labeling(Vars), AllSolutions). write_all :- setup_constraints(Vars), ( labeling(Vars), writeln(Vars), fail ; true ).
Best Solution: Optimization • Branch-and-bound method • finding the best of many solutions • without checking them all • :- lib(branch_and_bound). • Search code for all-solutions can simply be wrapped into the optimisation primitive: • bb_min(labeling(Vars), Cost, Options) • Options: • Strategy: continue, restart, dichotomic • Initial cost bounds (if known) • Minimum improvement (absolute/percentage) between solutions • Timeout
First solution Better solution Optimal solution No solution 1 2 3 4 Branch-and-bound (incremental) Cost Solu- tions Lower bound (relaxed solution) Iterations:
Impact of search strategy on b&b • Search space size 5, unlucky • ?- X::1..5, Cost #= 6-X, minimize(labeling([X]), Cost). • Found a solution with cost 5 • Found a solution with cost 4 • Found a solution with cost 3 • Found a solution with cost 2 • Found a solution with cost 1 • X=5 • Cost=1 • Search space size 5, lucky • ?- X::1..5, Cost #= X, minimize(labeling([X]), Cost). • Found a solution with cost 1 • X=1 • Cost=1
First solution Better solution Optimal solution No solution 1 2 3 4 5 6 Branch-and-bound (dichotomic strategy) Cost Solu- tions Lower bound (relaxed solution) Iterations:
Using dichotomic b&b strategy • Part of search space (solution 4) skipped: • ?- X::1..5, Cost #= 6-X, bb_min(labeling([X]), Cost, • bb_options with strategy:dichotomic). • Found a solution with cost 5 • Found a solution with cost 3 • Found a solution with cost 2 • Found a solution with cost 1 • X = 5 • Cost = 1
All solutions, timing and counting all :- setup_constraints(Vars), Tstart is cputime, setval(solutions, 0), % a non-logical counter ( labeling(Vars), % search incval(solutions), % solution found fail % backtrack ; true ), getval(solutions, Sols), Time is cputime - Tstart, printf("%w solutions found in %w seconds\n", [Sols,Time]).
Computing the remaining search space labeling(AllVars) :- ( fromto(OrderedVars, [X|RestVars], RestVars, []) do indomain(X), search_space_size(RestVars, Size), printf("Remaining search space: %d\n", Size) ). search_space_size(List, Size) :- ( foreach(X, List), fromto(1,Size0,Size1,Size) do get_ic_domain_size(X, SizeX), Size1 is Size0*SizeX ).
Counting backtracks labeling(AllVars) :- setval(backtracks, 0), ( fromto(AllVars, [X|RestVars], RestVars, []) do count_backtracks, indomain(X) ), getval(backtracks, BT), printf("Solution found after %d backtracks.\n", BT). count_backtracks :- setval(deep_fail,false). count_backtracks :- getval(deep_fail,false), setval(deep_fail,true), incval(backtracks), fail. • There are different ways of counting. • This method has these properties: • immediate failure due to propagation • does not count as a backtrack • the optimal value for N solutions is N Exercise: How does this work?