@ -23,7 +23,7 @@
( if ( null? argss )
( yield ( reverse acc ) )
( for ( [ arg ( car argss ) ] )
( loop ( cdr argss ) ( cons arg acc ) ) ) ) ) ) ) )
( loop ( cdr argss ) ( cons arg acc ) ) ) ) ) ) ) )
( struct csp ( vars constraints ) #:mutable #:transparent )
( define constraints csp-constraints )
@ -39,7 +39,7 @@
( raise-argument-error ' constraint " csp " prob ) )
;; apply proc in many-to-many style
( for/and ( [ args ( in-cartesian ( map ( λ ( name ) ( find-domain prob name ) ) ( constraint-names const ) ) ) ] )
( apply ( constraint-proc const ) args ) ) ) )
( apply ( constraint-proc const ) args ) ) ) )
( define name? symbol? )
@ -101,27 +101,35 @@
( ( csp? name? ) ( ( or/c ( listof any/c ) procedure? ) ) . ->* . void? )
( add-vars! prob ( list name ) vals-or-procedure ) )
( define/contract ( add-constraints! prob proc namess [ proc-name #false ] )
( define/contract ( add-constraints! prob proc namess [ proc-name #false ]
#:caller [ caller-id ' add-constraints! ] )
( ( csp? procedure? ( listof ( listof name? ) ) ) ( ( or/c #false name? ) ) . ->* . void? )
( unless ( procedure? proc )
( raise-argument-error caller-id " procedure " proc ) )
( unless ( and ( list? namess ) ( andmap ( λ ( ns ) ( and ( list? ns ) ( andmap name? ns ) ) ) namess ) )
( raise-argument-error caller-id " list of lists of names " namess ) )
( set-csp-constraints! prob ( append ( constraints prob )
( for/list ( [ names ( in-list namess ) ] )
( for ( [ name ( in-list names ) ] )
( check-name-in-csp! ' add-constraints! prob name ) )
( make-constraint names ( if proc-name
( procedure-rename proc proc-name )
proc ) ) ) ) ) )
( for ( [ name ( in-list names ) ] )
( check-name-in-csp! ' add-constraints! prob name ) )
( make-constraint names ( if proc-name
( procedure-rename proc proc-name )
proc ) ) ) ) ) )
( define/contract ( add-pairwise-constraint! prob proc names [ proc-name #false ] )
( ( csp? procedure? ( listof name? ) ) ( name? ) . ->* . void? )
( add-constraints! prob proc ( combinations names 2 ) proc-name ) )
( unless ( and ( list? names ) ( andmap name? names ) )
( raise-argument-error ' add-pairwise-constraint! " list of names " names ) )
( add-constraints! prob proc ( combinations names 2 ) proc-name #:caller ' add-pairwise-constraint! ) )
( define/contract ( add-constraint! prob proc names [ proc-name #false ] )
( ( csp? procedure? ( listof name? ) ) ( name? ) . ->* . void? )
( add-constraints! prob proc ( list names ) proc-name ) )
( add-constraints! prob proc ( list names ) proc-name #:caller ' add-constraint! ) )
( define/contract ( alldiff = x y )
( define/contract ( alldiff x y )
( any/c any/c . -> . boolean? )
( not ( = x y ) ) )
( define alldiff= alldiff )
( struct backtrack ( histories ) #:transparent )
( define ( backtrack! [ names null ] ) ( raise ( backtrack names ) ) )
@ -148,7 +156,7 @@
( check-name-in-csp! ' find-var prob name )
( for/first ( [ vr ( in-vars prob ) ]
#:when ( eq? name ( var-name vr ) ) )
vr ) )
vr ) )
( define/contract ( find-domain prob name )
( csp? name? . -> . ( listof any/c ) )
@ -191,20 +199,20 @@
( ormap assigned? ( constraint-names constraint ) ) )
( make-csp ( vars prob )
( for/list ( [ const ( in-constraints prob ) ] )
( cond
;; no point reducing 2-arity functions because they will be consumed by forward checking
[ ( and ( or ( not minimum-arity ) ( <= minimum-arity ( constraint-arity const ) ) )
( partially-assigned? const ) )
( match-define ( constraint cnames proc ) const )
;; pattern is mix of values and boxed symbols (indicating variables to persist)
;; use boxes here as cheap way to distinguish id symbols from value symbols
( define arity-reduction-pattern ( for/list ( [ cname ( in-list cnames ) ] )
( if ( assigned? cname )
( first ( find-domain prob cname ) )
( box cname ) ) ) )
( constraint ( filter-not assigned? cnames )
( reduce-function-arity proc arity-reduction-pattern ) ) ]
[ else const ] ) ) ) )
( cond
;; no point reducing 2-arity functions because they will be consumed by forward checking
[ ( and ( or ( not minimum-arity ) ( <= minimum-arity ( constraint-arity const ) ) )
( partially-assigned? const ) )
( match-define ( constraint cnames proc ) const )
;; pattern is mix of values and boxed symbols (indicating variables to persist)
;; use boxes here as cheap way to distinguish id symbols from value symbols
( define arity-reduction-pattern ( for/list ( [ cname ( in-list cnames ) ] )
( if ( assigned? cname )
( first ( find-domain prob cname ) )
( box cname ) ) ) )
( constraint ( filter-not assigned? cnames )
( reduce-function-arity proc arity-reduction-pattern ) ) ]
[ else const ] ) ) ) )
( define nassns 0 )
( define nfchecks 0 )
@ -219,9 +227,9 @@
( begin0
( make-csp
( for/list ( [ vr ( in-vars prob ) ] )
( if ( eq? name ( var-name vr ) )
( assigned-var name ( list val ) )
vr ) )
( if ( eq? name ( var-name vr ) )
( assigned-var name ( list val ) )
vr ) )
( constraints prob ) )
( when-debug ( set! nassns ( add1 nassns ) ) ) ) )
@ -251,7 +259,7 @@
( for/list ( [ x ( in-list xs ) ]
[ val ( in-list vals ) ]
#:when ( = val target-val ) )
x ) ] ) )
x ) ] ) )
( define/contract ( argmax* proc xs )
( procedure? ( listof any/c ) . -> . ( listof any/c ) )
@ -276,7 +284,7 @@
( csp? var? . -> . natural? )
( for/sum ( [ const ( in-constraints prob ) ]
#:when ( memq ( var-name var ) ( constraint-names const ) ) )
1 ) )
1 ) )
( define/contract ( domain-length var )
( var? . -> . natural? )
@ -285,7 +293,7 @@
( define/contract ( state-count csp )
( csp? . -> . natural? )
( for/product ( [ vr ( in-vars csp ) ] )
( domain-length vr ) ) )
( domain-length vr ) ) )
( define/contract ( mrv-degree-hybrid prob )
( csp? . -> . ( or/c #f var? ) )
@ -304,8 +312,8 @@
[ cnames ( in-value ( constraint-names const ) ) ]
#:when ( and ( = ( length names ) ( length cnames ) )
( for/and ( [ name ( in-list names ) ] )
( memq name cnames ) ) ) )
const ) )
( memq name cnames ) ) ) )
const ) )
( define ( one-arity? const ) ( = 1 ( constraint-arity const ) ) )
( define ( two-arity? const ) ( = 2 ( constraint-arity const ) ) )
@ -319,7 +327,7 @@
( ( listof ( and/c constraint? two-arity? ) ) . -> . ( listof arc? ) )
( for*/list ( [ const ( in-list constraints ) ]
[ name ( in-list ( constraint-names const ) ) ] )
( arc name const ) ) )
( arc name const ) ) )
( require sugar/debug )
( define/contract ( reduce-domain prob ark )
@ -331,16 +339,16 @@
( λ ( val other-val ) ( constraint-proc other-val val ) ) ) ) ; otherwise reverse arg order
( define ( satisfies-arc? val )
( for/or ( [ other-val ( in-list ( find-domain prob other-name ) ) ] )
( proc val other-val ) ) )
( proc val other-val ) ) )
( make-csp
( for/list ( [ vr ( in-vars prob ) ] )
( cond
[ ( assigned-var? vr ) vr ]
[ ( eq? name ( var-name vr ) )
( make-var name ( match ( filter satisfies-arc? ( domain vr ) )
[ ( ? empty? ) ( backtrack! ) ]
[ vals vals ] ) ) ]
[ else vr ] ) )
( cond
[ ( assigned-var? vr ) vr ]
[ ( eq? name ( var-name vr ) )
( make-var name ( match ( filter satisfies-arc? ( domain vr ) )
[ ( ? empty? ) ( backtrack! ) ]
[ vals vals ] ) ) ]
[ else vr ] ) )
( constraints prob ) ) )
( define/contract ( terminating-at? arcs name )
@ -349,7 +357,7 @@
#:when ( and
( memq name ( constraint-names ( arc-const arc ) ) )
( not ( eq? name ( arc-name arc ) ) ) ) )
arc ) )
arc ) )
( define/contract ( ac-3 prob ref-name )
( csp? name? . -> . csp? )
@ -360,8 +368,8 @@
( two-arity-constraints->arcs ( for/list ( [ const ( in-constraints prob ) ]
#:when ( and ( two-arity? const )
( for/and ( [ cname ( in-list ( constraint-names const ) ) ] )
( memq cname checkable-names ) ) ) )
const ) ) )
( memq cname checkable-names ) ) ) )
const ) ) )
( for/fold ( [ prob prob ]
[ arcs ( sort starting-arcs < #:key ( λ ( a ) ( length ( find-domain prob ( arc-name a ) ) ) ) #:cache-keys? #true ) ]
#:result ( prune-singleton-constraints prob ) )
@ -390,11 +398,11 @@
( define ref-val ( first ( find-domain prob ref-name ) ) )
( define new-vals
( for/set ( [ val ( in-set vals ) ]
#:when ( for/and ( [ const ( in-list constraints ) ] )
( match const
[ ( constraint ( list ( == name eq? ) _ ) proc ) ( proc val ref-val ) ]
[ ( constraint _ proc ) ( proc ref-val val ) ] ) ) )
val ) )
#:when ( for/and ( [ const ( in-list constraints ) ] )
( match const
[ ( constraint ( list ( == name eq? ) _ ) proc ) ( proc val ref-val ) ]
[ ( constraint _ proc ) ( proc ref-val val ) ] ) ) )
val ) )
( checked-variable name new-vals ( cons ( cons ref-name ref-val ) ( match vr
[ ( checked-variable _ _ history ) history ]
[ _ null ] ) ) ) ] ) ] ) )
@ -403,15 +411,15 @@
( ( csp? ) ( ( or/c #false name? ) ) . ->* . csp? )
( define singleton-var-names ( for/list ( [ vr ( in-vars prob ) ]
#:when ( singleton-var? vr ) )
( var-name vr ) ) )
( var-name vr ) ) )
( make-csp
( vars prob )
( for/list ( [ const ( in-constraints prob ) ]
#:unless ( and ( two-arity? const )
( or ( not ref-name ) ( constraint-relates? const ref-name ) )
( for/and ( [ cname ( in-list ( constraint-names const ) ) ] )
( memq cname singleton-var-names ) ) ) )
const ) ) )
( memq cname singleton-var-names ) ) ) )
const ) ) )
( define/contract ( forward-check prob ref-name )
( csp? name? . -> . csp? )
@ -420,7 +428,7 @@
;; conflict-set will be empty if there are no empty domains (as we would hope)
( define conflict-set ( for/list ( [ cvr ( in-list checked-vars ) ]
#:when ( set-empty? ( domain cvr ) ) )
( history cvr ) ) )
( history cvr ) ) )
;; for conflict-directed backjumping it's essential to forward-check ALL vars
;; (even after an empty domain is generated) and combine their conflicts
;; so we can discover the *most recent past var* that could be the culprit.
@ -437,7 +445,7 @@
;; constraint is checkable if all constraint names
;; are in target list of names.
( for/and ( [ cname ( in-list ( constraint-names const ) ) ] )
( memq cname names ) ) )
( memq cname names ) ) )
( define/contract ( constraint-arity const )
( constraint? . -> . natural? )
@ -454,21 +462,21 @@
( partition ( λ ( const ) ( and ( constraint-checkable? const assigned-varnames )
( or ( not mandatory-names )
( for/and ( [ name ( in-list mandatory-names ) ] )
( constraint-relates? const name ) ) ) ) )
( constraint-relates? const name ) ) ) ) )
( constraints prob ) ) )
( cond
[ conflict-count?
( define conflict-count
( for/sum ( [ constraint ( in-list checkable-consts ) ]
#:unless ( constraint prob ) )
1 ) )
1 ) )
( when-debug ( set! nchecks ( + conflict-count nchecks ) ) )
conflict-count ]
[ else
( for ( [ ( constraint idx ) ( in-indexed checkable-consts ) ]
#:unless ( constraint prob ) )
( when-debug ( set! nchecks ( + ( add1 idx ) nchecks ) ) )
( backtrack! ) )
( when-debug ( set! nchecks ( + ( add1 idx ) nchecks ) ) )
( backtrack! ) )
;; discard checked constraints, since they have no further reason to live
( make-csp ( vars prob ) other-consts ) ] ) )
@ -480,19 +488,19 @@
prob
( make-csp
( for/list ( [ vr ( in-vars prob ) ] )
( match-define ( var name vals ) vr )
( define name-constraints ( filter ( λ ( const ) ( constraint-relates? const name ) ) unary-constraints ) )
( make-var name ( for/set ( [ val ( in-set vals ) ]
#:when ( for/and ( [ const ( in-list name-constraints ) ] )
( ( constraint-proc const ) val ) ) )
val ) ) )
( match-define ( var name vals ) vr )
( define name-constraints ( filter ( λ ( const ) ( constraint-relates? const name ) ) unary-constraints ) )
( make-var name ( for/set ( [ val ( in-set vals ) ]
#:when ( for/and ( [ const ( in-list name-constraints ) ] )
( ( constraint-proc const ) val ) ) )
val ) ) )
other-constraints ) ) )
( define ( ( make-hist-proc assocs ) . xs )
( not
( for/and ( [ x ( in-list xs ) ]
[ val ( in-list ( map cdr assocs ) ) ] )
( equal? x val ) ) ) )
( equal? x val ) ) ) )
( define/contract ( backtracking-solver
prob
@ -513,7 +521,7 @@
( and ( backtrack? exn ) ( or ( let ( [ bths ( backtrack-histories exn ) ] )
( or ( empty? bths ) ( for*/or ( [ bth bths ]
[ rec bth ] )
( eq? name ( car rec ) ) ) ) ) ) ) )
( eq? name ( car rec ) ) ) ) ) ) ) )
( for/fold ( [ conflicts null ]
#:result ( void ) )
( [ val ( in-list ( order-domain-values ( set->list domain ) ) ) ] )
@ -523,7 +531,7 @@
( append conflicts ( remq name ( remove-duplicates
( for*/list ( [ bth ( in-list bths ) ]
[ rec ( in-list bth ) ] )
( car rec ) ) eq? ) ) ) ) ] )
( car rec ) ) eq? ) ) ) ) ] )
( let* ( [ prob ( assign-val prob name val ) ]
;; reduce constraints before inference,
;; to create more forward-checkable (binary) constraints
@ -565,9 +573,9 @@
( ( csp? ) ( integer? ) . ->* . generator? )
( generator ( )
( for ( [ thread-count ( or ( current-thread-count ) 1 ) ] ) ; todo: what is ideal thread count?
( make-min-conflcts-thread prob thread-count max-steps ) )
( make-min-conflcts-thread prob thread-count max-steps ) )
( for ( [ i ( in-naturals ) ] )
( yield ( thread-receive ) ) ) ) )
( yield ( thread-receive ) ) ) ) )
( define/contract ( optimal-stop-min proc xs )
( procedure? ( listof any/c ) . -> . any/c )
@ -575,7 +583,7 @@
( define threshold ( argmin proc sample ) )
( or ( for/first ( [ candidate ( in-list candidates ) ]
#:when ( <= ( proc candidate ) threshold ) )
candidate )
candidate )
( last candidates ) ) )
( define/contract ( conflicted-variable-names prob )
@ -583,7 +591,7 @@
;; Return a list of variables in current assignment that are conflicted
( for/list ( [ name ( in-var-names prob ) ]
#:when ( positive? ( nconflicts prob name ) ) )
name ) )
name ) )
( define/contract ( min-conflicts-value prob name vals )
( csp? name? ( listof any/c ) . -> . any/c )
@ -592,7 +600,7 @@
#:cache-keys? #true ) )
( for/first ( [ val ( in-list vals-by-conflict ) ]
#:unless ( equal? val ( first ( find-domain prob name ) ) ) ) ;; but change the value
val ) )
val ) )
( define no-value-sig ( gensym ) )
@ -607,11 +615,11 @@
( ( csp? ) ( ( listof name? ) ) . ->* . ( listof ( cons/c name? any/c ) ) )
( define assocs
( for/list ( [ vr ( in-vars prob ) ] )
( match vr
[ ( var name ( list val ) ) ( cons name val ) ] ) ) )
( match vr
[ ( var name ( list val ) ) ( cons name val ) ] ) ) )
( if keys
( for/list ( [ key ( in-list keys ) ] )
( assq key assocs ) )
( assq key assocs ) )
assocs ) )
( define/contract ( combine-csps probs )
@ -626,16 +634,16 @@
( make-csp
( for/list ( [ vr ( in-vars prob ) ]
#:when ( memq ( var-name vr ) names ) )
vr )
vr )
( for/list ( [ const ( in-constraints prob ) ]
#:when ( constraint-checkable? const names ) )
const ) ) )
const ) ) )
( define ( decompose-prob prob )
; decompose into independent csps. `cc` determines "connected components"
( if ( current-decompose )
( for/list ( [ nodeset ( in-list ( cc ( csp->graph prob ) ) ) ] )
( extract-subcsp prob nodeset ) )
( extract-subcsp prob nodeset ) )
( list prob ) ) )
( define ( make-solution-generator prob )
@ -643,10 +651,10 @@
( define subprobs ( decompose-prob prob ) )
( define solgens ( map ( current-solver ) subprobs ) )
( define solstreams ( for/list ( [ solgen ( in-list solgens ) ] )
( for/stream ( [ sol ( in-producer solgen ( void ) ) ] )
sol ) ) )
( for/stream ( [ sol ( in-producer solgen ( void ) ) ] )
sol ) ) )
( for ( [ solution-pieces ( in-cartesian solstreams ) ] )
( yield ( combine-csps solution-pieces ) ) ) ) )
( yield ( combine-csps solution-pieces ) ) ) ) )
( define-syntax-rule ( in-solutions PROB )
( in-producer ( make-solution-generator PROB ) ( void ) ) )
@ -660,7 +668,7 @@
( parameterize ( [ current-solver ( or solver ( current-solver ) backtracking-solver ) ] )
( for/list ( [ sol ( in-solutions prob ) ]
[ idx ( in-range max-solutions ) ] )
( finish-proc sol ) ) ) )
( finish-proc sol ) ) ) )
( define/contract ( solve prob
#:finish-proc [ finish-proc ( λ ( p ) ( csp->assocs p ( map var-name ( vars prob ) ) ) ) ]