Post on 13-Jan-2016
transcript
Implementing Constraints
2
Overview
A look at the literatureCSPs
Arc consistency algorithms
Implementation in ECLiPSe:Low level: Suspensions and Attributes
Prototyping in ECLiPSe:Intermediate: Constraint Handling Rules (CHR)
High level: Propia
A look at the literatureCSPs
Arc consistency algorithms
Implementation in ECLiPSe:Low level: Suspensions and Attributes
Prototyping in ECLiPSe:Intermediate: Constraint Handling Rules (CHR)
High level: Propia
3
Constraint Satisfaction Problems (CSP)
Much of the literature talks about “CSPs”
This usually refers to binary CSPs:
A fixed set of variables X1,…Xn
Every variable has a finite domain Di arbitrary domain, does not have to be ordered
Binary (2-variable) constraints only cij(Xi,Xj) constraint defined as sets of consistent value pairs
Any CSP can be transformed to binary CSPWe don’t normally do that
Instead generalise binary techniques
4
Static properties of a CSP network
Node consistencyvDi: ci(v)
Not very interesting
Arc consistencyvDi w Dj : cij(v,w)
Most relevant
Path consistencyvDi wDj u Dk: cik(v,u),ckj(u,w)
Usually too expensive
…
5
Arc consistency algorithms
Consistency in networks of relations [AC1-3]A.K. Mackworth, in Artificial Intelligence 8, pages 99-118, 1977.
Arc and path consistency revised [AC4]R. Mohr and T.C. Henderson, in Artificial Intelligence 28, pages 225-233, 1986.
A generic arc-consistency algorithm and its specializations [AC5]P. Van Hentenryck, Y. Deville, and C.-M. Teng, in Artificial Intelligence 57, pages 291-
321, 1992.
Arc-consistency and arc-consistency again [AC6]C. Bessiere, in Artificial Intelligence 65, pages 179-190, 1994.
Using constraint metaknowledge to reduce arc consistency
computation [AC7]C. Bessiere, E.C. Freuder, and J.-R. Régin, in Artificial Intelligence 107, pages 125-
148, 1999.
6
AC-1
procedure REVISE(Vi,Vj) DELETE <- false; for each X in Di do if there is no such Y in Dj such that (X,Y) is consistent, then delete X from Di; DELETE <- true; endif; endfor; return DELETE;
procedure AC-1 end REVISE Q <- {(Vi,Vj) in arcs(G),i#j}; repeat CHANGE <- false; for each (Vi,Vj) in Q do CHANGE <- REVISE(Vi,Vj) or CHANGE; endfor until not(CHANGE) end AC-1
G is the constraint graph, Q is a queue
7
AC-3
procedure AC-3 Q <- {(Vi,Vj) in arcs(G),i#j}; while not Q empty select and delete any arc (Vk,Vm) from Q; if REVISE(Vk,Vm) then Q <- Q union {(Vi,Vk) such that
(Vi,Vk) in arcs(G),i#k,i#m}
endif endwhile end AC-3
Re-check only arcs that were affected Queue contains arcs (constraints) This is a practical algorithm Easy to generalise to n-ary constraints
8
AC-4 - initialization
procedure INITIALIZE Q <- {}; S <- {}; % initialize each element of structure S for each (Vi,Vj) in arcs(G) do % (Vi,Vj) and (Vj,Vi) are same
elements for each a in Di do total <- 0; for each b in Dj do if (a,b) is consistent according to the constraint (Vi,Vj) then total <- total+1; Sj,b <- Sj,b union {<i,a>}; endif endfor; counter[(i,j),a] <- total; if counter[(i,j),a]=0 then delete a from Di; Q <- Q union {<i,a>}; endif; endfor; endfor; return Q; end INITIALIZE
9
AC-4 - fixpoint computation
procedure AC-4 Q <- INITIALIZE; while not Q empty select and delete any pair <j,b> from Q; counter[(i,j),a] <- counter[(i,j),a] - 1; for each <i,a> from Sj,b do if counter[(i,j),a]=0 & a is still in Di then delete a from Di; Q <- Q union {<i,a>}; endif endfor endwhile end AC-4
Maintains support counters for each domain element
Queue contains information about the deleted domain value
10
Relation to Search
These algorithms look at the constraint network statically
They are not strong enough to enforce global consistency
of the network, so we still need search
Search decisions change some domain(s), which means
we may lose any achieved xxx-consistency
Overall design needs to answer 2 questions: What level(s) of consistency do we want to employ? At what time (during search) do we want which consistency?
11
Consistency during search
Generate and testInstantiate all problem variables, then check all constraints
Standard backtrackingCheck every constraint as soon as both its variables are instantiated
Forward Checking (~ Label Propagation) Look ahead
When one variable is instantiated make the constraint arc consistent
When a variable is instantiated, make the whole graph arc consistent again (maintain arc consistency at all times)
(This terminology used in P.v.Hentenryck: Constraint Satisfaction in LP)
12
Other forms of consistency
Interval (bounds) consistencyUseful for ordered domainsWe don’t look at every domain valueOnly make sure that smallest and largest domain value are consistentDepending on the constraint semantics, bounds consistency implies arc
consistency, or not
Used in many ECLiPSe librarieslib(fd) - integer domainlib(ic) - integer and real number domainlib(ic_sets) - integer set domainlib(ic_symbolic) - ordered symbolic domain…
Implementing Constraints with Suspensions and Atrributes
14
What you need to know
To implement additional constraint for existing solver: suspend / resume mechanism the solver’s domain access interface
To implement a solver over a new domain: variable attribute mechanism
15
Basic Programming Support
Suspending the execution of goalsdelay-clause or suspend/3,4
Corresponds to the queue in AC-3!
Data/Event-driven waking on changeattaching to variables + condition
Change notifications Allows to say when computation should happen
messages from variables to constraints (see lib(notify_ports)) Information about what changed (for AC-4 style algorithms)
Priorities for goals Allows to tune efficiency
Suspending the execution of goalsdelay-clause or suspend/3,4
Corresponds to the queue in AC-3!
Data/Event-driven waking on changeattaching to variables + condition
Change notifications Allows to say when computation should happen
messages from variables to constraints (see lib(notify_ports)) Information about what changed (for AC-4 style algorithms)
Priorities for goals Allows to tune efficiency
16
Resolvent in ECLiPSe
q1, …, qm,
schedule(wake)
p1 , … , pn . Delayed Goals
suspend (delay)
s1
s4sl
s2 s3
r1, …, rk,
Prio 1 Prio 2 … Prio 12
17
Consistency check
capacity(1, N) :- N>=0.0, N=<350.0.
capacity(2, N) :- N>=0.0, N=<180.0.
capacity(3, N) :- N>=0.0, N=<50.0.
capacity(1, N) :- N>=0.0, N=<350.0.
capacity(2, N) :- N>=0.0, N=<180.0.
capacity(3, N) :- N>=0.0, N=<50.0.
delay capacity(T,N) if var(T);var(N).delay capacity(T,N) if var(T);var(N).
capacity(T,N) :- (var(T);var(N)), !,
suspend(capacity(T,N), 0, [T,N]->inst).
capacity(1, N) :- N>=0.0, N=<350.0.
capacity(2, N) :- N>=0.0, N=<180.0.
capacity(3, N) :- N>=0.0, N=<50.0.
capacity(T,N) :- (var(T);var(N)), !,
suspend(capacity(T,N), 0, [T,N]->inst).
capacity(1, N) :- N>=0.0, N=<350.0.
capacity(2, N) :- N>=0.0, N=<180.0.
capacity(3, N) :- N>=0.0, N=<50.0.
Declarative style
Imperative style
18
Forward Checking
:- lib(ic).
delay capacity(Type,N) if var(Type),var(N).
capacity(Type, N) :- var(N),
( Type=1 -> N :: 0.0..350.0
; Type=2 -> N :: 0.0..180.0
; Type=3 -> N :: 0.0..50.0
; fail
).
capacity(Type, N) :- nonvar(N),
N>=0.0,
( N=<50.0 -> Type :: [1,2,3]
; N=<180.0 -> Type :: [1,2]
; N=<350.0 -> Type = 1
; fail
).
:- lib(ic).
delay capacity(Type,N) if var(Type),var(N).
capacity(Type, N) :- var(N),
( Type=1 -> N :: 0.0..350.0
; Type=2 -> N :: 0.0..180.0
; Type=3 -> N :: 0.0..50.0
; fail
).
capacity(Type, N) :- nonvar(N),
N>=0.0,
( N=<50.0 -> Type :: [1,2,3]
; N=<180.0 -> Type :: [1,2]
; N=<350.0 -> Type = 1
; fail
).
19
Constraint via Propagation Goals
X Yc
X Y
c_fwd
c_bwd
X Yc_prop
Alternativelyimplemented
as
20
Forward Checking - 2 agents
capacity(Type, N) :-
capacity_forward(Type, N),
capacity_backward(Type, N).
delay capacity_forward(Type, _N) if var(Type).
capacity_forward(Type, N) :-
( Type=1 -> N :: 0.0..350.0
; Type=2 -> N :: 0.0..180.0
; Type=3 -> N :: 0.0..50.0
; fail ).
delay capacity_backward(_Type, N) if var(N).
capacity_backward(Type, N) :- N>=0.0,
( N=<50.0 -> Type :: [1,2,3]
; N=<180.0 -> Type :: [1,2]
; N=<350.0 -> Type = 1
; fail ).
capacity(Type, N) :-
capacity_forward(Type, N),
capacity_backward(Type, N).
delay capacity_forward(Type, _N) if var(Type).
capacity_forward(Type, N) :-
( Type=1 -> N :: 0.0..350.0
; Type=2 -> N :: 0.0..180.0
; Type=3 -> N :: 0.0..50.0
; fail ).
delay capacity_backward(_Type, N) if var(N).
capacity_backward(Type, N) :- N>=0.0,
( N=<50.0 -> Type :: [1,2,3]
; N=<180.0 -> Type :: [1,2]
; N=<350.0 -> Type = 1
; fail ).
21
Explicitly Suspending Goals
Providing more flexibility than delay-clauses Creation
make_suspension(+Goal, +Priority, -Suspension)
Attaching to attributed variableinsert_suspension(+Vars, +Suspension, +Index,
+AttributeName)
Combined create & attachsuspend(+Goal, +Priority, +Condition)suspend(+Goal, +Priority, +Condition, -Suspension)
Delayed goal viewer shows suspended goals
Providing more flexibility than delay-clauses Creation
make_suspension(+Goal, +Priority, -Suspension)
Attaching to attributed variableinsert_suspension(+Vars, +Suspension, +Index,
+AttributeName)
Combined create & attachsuspend(+Goal, +Priority, +Condition)suspend(+Goal, +Priority, +Condition, -Suspension)
Delayed goal viewer shows suspended goals
22
Triggering of suspensions
When goals (constraints) are suspended, they are usually
attached to variables with trigger conditions:
X->instwhen X becomes instantiated (most specific)
X->constrainedwhen X becomes constrained in any way (most general)
X->ic:min, X->ic:max, X->ic:hole, X->typewhen the lower bound / upper bound / other value in the domain of X changes
Other trigger conditions are defined by the various
solvers
When goals (constraints) are suspended, they are usually
attached to variables with trigger conditions:
X->instwhen X becomes instantiated (most specific)
X->constrainedwhen X becomes constrained in any way (most general)
X->ic:min, X->ic:max, X->ic:hole, X->typewhen the lower bound / upper bound / other value in the domain of X changes
Other trigger conditions are defined by the various
solvers
23
Bounds consistency
ge(X, Y) :-
get_max(X, XH), % get current
bounds
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
ge(X, Y) :-
get_max(X, XH), % get current
bounds
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
X >= Y
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [[X,Y]->constrained])
; true ),
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [[X,Y]->constrained])
; true ),
Bounds-consistent greater-equal:Bounds-consistent greater-equal:
24
More precise waking conditions
ge(X, Y) :-
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [X->ic:max,Y->ic:min])
; true ),
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL),
impose_max(Y, XH).
ge(X, Y) :-
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [X->ic:max,Y->ic:min])
; true ),
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL),
impose_max(Y, XH).
X >= YBounds-consistent greater-equal:Bounds-consistent greater-equal:
25
Variables, Attributes and Suspended Goals
X { suspend: inst constrained ic: 1..9 min max hole}
X { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
ge(X,Y) ge(X,Y)
26
Directional propagators
ge(X, Y) :-
ge_fwd(X,Y),
ge_bwd(X,Y).
ge_fwd(X, Y) :-
( var(X) -> suspend(ge_fwd(X,Y), 0, [X->ic:max])
; true ),
get_max(X, XH),
impose_max(Y, XH).
ge_bwd(X, Y) :-
( var(Y) -> suspend(ge_bwd(X,Y), 0, [Y->ic:min])
; true ),
get_min(Y, YL),
impose_min(X, YL).
ge(X, Y) :-
ge_fwd(X,Y),
ge_bwd(X,Y).
ge_fwd(X, Y) :-
( var(X) -> suspend(ge_fwd(X,Y), 0, [X->ic:max])
; true ),
get_max(X, XH),
impose_max(Y, XH).
ge_bwd(X, Y) :-
( var(Y) -> suspend(ge_bwd(X,Y), 0, [Y->ic:min])
; true ),
get_min(Y, YL),
impose_min(X, YL).
X >= Y
27
ge_bwd(X,Y) ge_bwd(X,Y)
ge_fwd(X,Y) ge_fwd(X,Y)
Directional Propagators
X { suspend: inst constrained ic: 1..9 min max hole}
X { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
28
Repeated waking on domain updates
E.g. propagating boundsge(X, Y) :-
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [X->ic:max, Y->ic:min])
; true ),
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
Propertiesvariables remain variablesarbitrary number of steps (limited only by domain size)
Potential performance problem:re-suspending identical goal over and over again!
E.g. propagating boundsge(X, Y) :-
( var(X),var(Y) ->
suspend(ge(X,Y), 0, [X->ic:max, Y->ic:min])
; true ),
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
Propertiesvariables remain variablesarbitrary number of steps (limited only by domain size)
Potential performance problem:re-suspending identical goal over and over again!
X >= Y
29
Domain/bounds propagation using a “demon”
Same constraint as before:ge(X, Y) :-
suspend(ge(X,Y,Susp), 0, [X->ic:max, Y->ic:min], Susp),
ge(X, Y, Susp).
:- demon ge/3. % demon declaration
ge(X, Y, Susp) :-
( var(X),var(Y) -> true % implicitly re-suspend
; kill_suspension(Susp) ), % explicit kill
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
A “demon” does not need to be re-suspendedWhen woken, it splits into a woken and a suspended instance
Needs to be killed explicitly when no longer needed
Same constraint as before:ge(X, Y) :-
suspend(ge(X,Y,Susp), 0, [X->ic:max, Y->ic:min], Susp),
ge(X, Y, Susp).
:- demon ge/3. % demon declaration
ge(X, Y, Susp) :-
( var(X),var(Y) -> true % implicitly re-suspend
; kill_suspension(Susp) ), % explicit kill
get_max(X, XH),
get_min(Y, YL),
impose_min(X, YL), % impose new bounds
impose_max(Y, XH).
A “demon” does not need to be re-suspendedWhen woken, it splits into a woken and a suspended instance
Needs to be killed explicitly when no longer needed
30
Variables, Attributes and Suspended Demon
X { suspend: inst constrained ic: 1..9 min max hole}
X { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
Y { suspend: inst constrained ic: 1..9 min max hole}
ge(X,Y,Susp) ge(X,Y,Susp)
31
Single-propagator max-constraint
mymax(A, B, M):- get_bounds(A, MinA, MaxA),
get_bounds(B, MinB, MaxB),get_bounds(M, MinM, MaxM),
( MinA >= MaxB -> A = M
; MinB >= MaxA -> B = M; MinM > MaxB -> A = M; MinM > MaxA -> B = M;
call_priority((Max is max(MaxA, MaxB),Min is max(MinA, MinB),impose_bounds(M, Min, Max), impose_max(A, MaxM),impose_max(B, MaxM),
Vars = [A,B,M],( nonground(2, Vars, _) ->
suspend(mymax(A, B, M), 3, [Vars->ic:max,Vars->ic:min]); true)
), 2)).
32
N-ary constraints
Even more implementation choices: Level of consistency Algorithm Eagerness
Trigger conditions and priority
Logical decompositione.g. alldifferent many disequality constraints
Operational decompositionOne or several propagator goals
Data-driven vs explicit fixpoint computation
33
Special case: Incremental checking (1)
Value X occurs in List :among(X, [Y|Ys]) :-
( var(Y) ->
suspend(among(X, [Y|Ys]),0,Y->inst)
; X = Y ->
true
;
among(X, Ys)
).
Propertiesefficient, looks at most once at each list element
delayed goal may be different after each step
success after 1..N steps or failure after N steps
Value X occurs in List :among(X, [Y|Ys]) :-
( var(Y) ->
suspend(among(X, [Y|Ys]),0,Y->inst)
; X = Y ->
true
;
among(X, Ys)
).
Propertiesefficient, looks at most once at each list element
delayed goal may be different after each step
success after 1..N steps or failure after N steps
34
Incremental checking (2)
Value X does not occur in List:not_among(X, []).not_among(X, [Y|Ys]) :-
( var(Y) -> suspend(not_among(X, [Y|Ys]),0,Y->inst); X \= Y, not_among(X, Ys)).
PropertiesSuccess after N steps or failure after 1..N stepsBut: there may be an X at the end of the list, behind a variable! Failure can be detected unnecessarily late
Value X does not occur in List:not_among(X, []).not_among(X, [Y|Ys]) :-
( var(Y) -> suspend(not_among(X, [Y|Ys]),0,Y->inst); X \= Y, not_among(X, Ys)).
PropertiesSuccess after N steps or failure after 1..N stepsBut: there may be an X at the end of the list, behind a variable! Failure can be detected unnecessarily late
35
Parallel checking
Value X does not occur in List:not_among(X, []).not_among(X, [Y|Ys]) :-
( var(Y) -> suspend(X\=Y, 0, Y->inst); X \= Y),
not_among(X, Ys).
PropertiesCan expand into up to N delayed goals X\=YFailure detected as soon as possible Normally more interesting than detecting success!
Value X does not occur in List:not_among(X, []).not_among(X, [Y|Ys]) :-
( var(Y) -> suspend(X\=Y, 0, Y->inst); X \= Y),
not_among(X, Ys).
PropertiesCan expand into up to N delayed goals X\=YFailure detected as soon as possible Normally more interesting than detecting success!
37
Implementing a new domain
:- module(enum). % library name
:- meta_attribute(enum, [ % attribute name and handlers
unify: unify_enum/2,
print: print_enum/2,
compare_instances: ...,
copy_term: ...]).
make_enum_variable(Var, Values) :- % constructor
Attr = enum(Values,_),
init_suspension_list(2, Attr),
add_attribute(Var, Attr).
unify_enum(Value, Attribute) :- % unify handler
check if Value is compatible with Attribute
exclude(Var{Attribute}, Value) ?- % domain access primitive
delete Value from possible values in Attribute
...
38
Exercise 1
Write a constraint
atmost(+N, +List, +Value) Meaning: at most N elements of List have value
Value Behaviour: fail as soon as more than N elements are
instantiated to Value Improvement: fail as soon as not enough variables
with Value in their domain are left over
Write a constraint
atmost(+N, +List, +Value) Meaning: at most N elements of List have value
Value Behaviour: fail as soon as more than N elements are
instantiated to Value Improvement: fail as soon as not enough variables
with Value in their domain are left over
39
Exercise 2
Write a constraintoffset(?X,+C,?Y)
which is like
offset(X,C,Y) :- Y #= X+C.But maintains domain consistency (propagates “holes”)
Use Suspension built-ins Domain access primitives from the ic_kernel module
40
Domain attribute access in lib(ic)
Getting domain representation from a variableget_domain_as_list(+Var, -ListOfValues)
get_bounds(+Var, -Min, -Max)
get_min(+Var, -Min)
get_max(+Var, -Max)
get_domain_size(+Var, -Size)
...
Updating a variable’s domainimpose_bounds(+Var, +Min, +Max)
impose_min(+Var, +Min)
impose_max(+Var, +Max)
exclude(+Var, +Value)
...
Getting domain representation from a variableget_domain_as_list(+Var, -ListOfValues)
get_bounds(+Var, -Min, -Max)
get_min(+Var, -Min)
get_max(+Var, -Max)
get_domain_size(+Var, -Size)
...
Updating a variable’s domainimpose_bounds(+Var, +Min, +Max)
impose_min(+Var, +Min)
impose_max(+Var, +Max)
exclude(+Var, +Value)
...