add-transitive-constraint! and other nits

main
Matthew Butterick 3 years ago
parent f6f2c943d8
commit 7f69bbdd56

@ -4,12 +4,11 @@
(current-inference forward-check)
(current-select-variable mrv)
(current-order-values shuffle)
(current-random #true)
;; queens problem
;; place queens on chessboard so they do not intersect
(define board-size 8)
(define board-size 10)
(define queens (make-csp))
(define qs (range board-size))
@ -25,12 +24,12 @@
(define (sol->string sol)
(define assocs (csp->assocs sol))
(string-join (for/list ([q (in-list (sort assocs < #:key car))])
(displayln (string-join (for/list ([q (in-list (sort assocs < #:key car))])
(apply string (add-between (for/list ([idx (in-range board-size)])
(if (= idx (cdr q)) #\@ #\·)) #\space))) "\n"))
assocs)
(current-thread-count 4)
(displayln (solve queens #:finish-proc sol->string))
(parameterize (#;[current-solver min-conflicts-solver])
(time (solve queens)))
(parameterize ([current-solver min-conflicts-solver])
(time (solve queens #:finish-proc sol->string)))

@ -4,7 +4,6 @@
(current-inference forward-check)
(current-select-variable mrv)
(current-order-values shuffle)
(current-random #true)
(define (word-value . xs)
(for/sum ([(x idx) (in-indexed (reverse xs))])

@ -112,12 +112,11 @@
#
|#
(define xsum (make-csp))
(add-vars! xsum '(l1 l2 l3 l4 r1 r2 r3 r4 x) (range 1 10))
(add-pairwise-constraint! xsum < '(l1 l2 l3 l4))
(add-pairwise-constraint! xsum < '(r1 r2 r3 r4))
(add-constraint! xsum (λ (l1 l2 l3 l4 x) (= 27 (+ l1 l2 l3 l4 x))) '(l1 l2 l3 l4 x))
(add-constraint! xsum (λ (r1 r2 r3 r4 x) (= 27 (+ r1 r2 r3 r4 x))) '(r1 r2 r3 r4 x))
(add-pairwise-constraint! xsum alldiff= '(l1 l2 l3 l4 r1 r2 r3 r4 x))
(add-vars! xsum '(1 2 3 4 5 6 7 8 9) '(1 2 3 4 5 6 7 8 9))
(add-transitive-constraint! xsum < '(1 2 4 5))
(add-transitive-constraint! xsum < '(6 7 8 9))
(add-constraints! xsum (λ xs (= 27 (apply + xs))) '((1 2 3 4 5) (6 7 3 8 9)))
(add-all-diff-constraint! xsum)
(check-equal? (length (time-named (solve* xsum))) 8)
(print-debug-info)
@ -151,7 +150,7 @@
(add-constraint! smm (λ (s e n d m o r y)
(= (+ (word-value s e n d) (word-value m o r e))
(word-value m o n e y))) '(s e n d m o r y))
(add-pairwise-constraint! smm alldiff= '(s e n d m o r y))
(add-all-diff-constraint! smm)
(check-equal? (parameterize ([current-select-variable mrv-degree-hybrid]) ; todo: why is plain mrv so bad on this problem?
(time-named (solve smm))) '((s . 9) (e . 5) (n . 6) (d . 7) (m . 1) (o . 0) (r . 8) (y . 2)))
(print-debug-info)
@ -222,7 +221,7 @@
(add-vars! zebra ps '(dogs snails foxes horses zebra))
(for ([vars (list ns cs ds ss ps)])
(add-pairwise-constraint! zebra neq? vars))
(add-all-diff-constraint! zebra vars #:proc eq?))
(define (xnor lcond rcond)
(or (and lcond rcond) (and (not lcond) (not rcond))))

@ -129,6 +129,14 @@
(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-transitive-constraint! prob proc names [proc-name #false])
((csp? procedure? (listof name?)) (name?) . ->* . void?)
(unless (and (list? names) (>= (length names) 2))
(raise-argument-error 'add-transitive-constraint! "list of two or more names" names))
(add-constraints! prob proc (for/list ([name (in-list names)]
[next (in-list (cdr names))])
(list name next)) proc-name #:caller 'add-transitive-constraint!))
(define/contract (add-constraint! prob proc names [proc-name #false])
((csp? procedure? (listof name?)) (name?) . ->* . void?)
(unless (list? names)
@ -141,23 +149,13 @@
(define alldiff= alldiff)
(define (add-all-diff-constraint! prob [names (map var-name (csp-vars prob))]
#:proc [equal-proc equal?])
#:same [equal-proc equal?])
(add-pairwise-constraint! prob (λ (x y) (not (equal-proc x y))) names
(string->symbol (format "all-diff-~a" (object-name equal-proc)))))
(struct backtrack (histories) #:transparent)
(define (backtrack! [names null]) (raise (backtrack names)))
(define current-select-variable (make-parameter #f))
(define current-order-values (make-parameter #f))
(define current-inference (make-parameter #f))
(define current-solver (make-parameter #f))
(define current-decompose (make-parameter #t))
(define current-thread-count (make-parameter 4))
(define current-node-consistency (make-parameter #f))
(define current-arity-reduction (make-parameter #t))
(define current-learning (make-parameter #f))
(define/contract (check-name-in-csp! caller prob name)
(symbol? csp? name? . -> . void?)
(define names (map var-name (vars prob)))
@ -413,12 +411,12 @@
[constraints
(define ref-val (first (find-domain prob ref-name)))
(define new-vals
(for/list ([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))
(for/list ([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))
(make-checked-var name new-vals (cons (cons ref-name ref-val) (match vr
[(checked-variable _ _ history) history]
[_ null])))])]))
@ -507,8 +505,8 @@
(match-define (var name vals) vr)
(define name-constraints (filter (λ (const) (constraint-relates? const name)) unary-constraints))
(make-var name (for/list ([val (in-set vals)]
#:when (for/and ([const (in-list name-constraints)])
((constraint-proc const) val)))
#:when (for/and ([const (in-list name-constraints)])
((constraint-proc const) val)))
val)))
other-constraints)))
@ -526,7 +524,7 @@
#:select-variable [select-unassigned-variable
(or (current-select-variable) first-unassigned-variable)]
#:order-values [order-domain-values (or (current-order-values) first-domain-value)]
#:inference [inference (or (current-inference) forward-check)])
#:inference [inference (or (current-inference) no-inference)])
((csp?) (#:select-variable procedure? #:order-values procedure? #:inference procedure?) . ->* . solver?)
(solver
(generator ()
@ -675,7 +673,7 @@
(extract-subcsp prob nodeset))
(list prob)))
(define (make-solution-generator prob max-solutions)
(define (make-solution-generator prob [max-solutions #false])
(generator ()
(define subprobs (decompose-prob prob))
(define solgens (map (current-solver) subprobs))
@ -683,20 +681,22 @@
(for/stream ([sol (in-producer solgen (void))])
sol)))
(for ([solution-pieces (in-cartesian solstreams)]
[count (in-range max-solutions)])
[count (in-range (or max-solutions +inf.0))])
(yield (combine-csps solution-pieces)))
(for-each solver-kill solgens)))
(define-syntax-rule (in-solutions PROB MAX-SOLUTIONS)
(in-producer (make-solution-generator PROB MAX-SOLUTIONS) (void)))
(define-syntax (in-solutions stx)
(syntax-case stx ()
[(_ PROB) #'(in-solutions PROB #false)]
[(_ PROB MAX-SOLUTIONS) #'(in-producer (make-solution-generator PROB MAX-SOLUTIONS) (void))]))
(define/contract (solve* prob [max-solutions +inf.0]
(define/contract (solve* prob [max-solutions #false]
#:finish-proc [finish-proc (λ (p) (csp->assocs p (map var-name (vars prob))))]
#:solver [solver #f])
((csp?) (natural? #:finish-proc procedure? #:solver procedure?) . ->* . (listof any/c))
(when-debug (reset-nassns!) (reset-nfchecks!) (reset-nchecks!))
(parameterize ([current-solver (or solver (current-solver) backtracking-solver)])
(parameterize ([current-solver (or solver (current-solver))])
(for/list ([sol (in-solutions prob max-solutions)])
(finish-proc sol))))
@ -712,3 +712,12 @@
(define (<> a b) (not (= a b)))
(define (neq? a b) (not (eq? a b)))
(define current-select-variable (make-parameter #f))
(define current-order-values (make-parameter #f))
(define current-inference (make-parameter forward-check))
(define current-solver (make-parameter backtracking-solver))
(define current-decompose (make-parameter #t))
(define current-thread-count (make-parameter 4))
(define current-node-consistency (make-parameter #f))
(define current-arity-reduction (make-parameter #t))
(define current-learning (make-parameter #f))

@ -324,9 +324,9 @@ Imperatively add a new constraint. The constraint applies the function @racket[_
@defproc[(add-all-diff-constraint!
[prob csp?]
[names (listof var-name?) (map var-name (csp-vars prob))]
[#:proc equal-proc equal?])
[#:same equal-proc equal?])
void?]{
Imperatively add an ``all diff'' constraint, which is a pairwise @racket[(compose1 not equal?)] constraint. A equality function other than @racket[equal?] can be passed via the @racket[#:proc] argument. There is nothing special about using this function vs. applying the constraint manually.
Imperatively add an ``all diff'' constraint, which is a pairwise @racket[(compose1 not equal?)] constraint. A equality function other than @racket[equal?] can be passed via the @racket[#:same] argument. There is nothing special about using this function vs. applying the constraint manually.
}
@ -383,6 +383,52 @@ Which would become:
This is better, but also overkill, because if @racket[(< a b)] and @racket[(< b c)], then by transitivity, @racket[(< a c)] is necessarily true. So this is a case where pairwise expands into more constraints than we actually need. This will not produce any wrong solutions, but especially on larger lists of variables, it creates unnecessary work that my slow down the solution search.
}
@defproc[(add-transitive-constraint!
[prob csp?]
[func procedure?]
[names (listof var-name?)]
[func-name (or/c #false var-name?) #f])
void?]{
Similar to @racket[add-pairwise-constraint!], but adds the constraint between every @italic{sequential} pair of names in @racket[_names] (not every @italic{possible} pair).
For instance, consider this use of @racket[add-pairwise-constraint!]:
@racketblock[
(add-pairwise-constraint! my-csp < '(a b c d))
]
This applies the constraint between every possible pair, so the result is equivalent to:
@racketblock[
(add-constraint! my-csp < '(a b))
(add-constraint! my-csp < '(a c))
(add-constraint! my-csp < '(a d))
(add-constraint! my-csp < '(b c))
(add-constraint! my-csp < '(b d))
(add-constraint! my-csp < '(c d))
]
This isn't wrong, but as any seventh grader could tell you, it's overkill. @racket[<] is a transitive relation, therefore if it's true that @racket[(< a b)] and @racket[(< b c)], it's necessarily also true that @racket[(< a c)]. So there's no need to apply a separate constraint for that.
This is the behavior we get from @racket[add-transitive-constraint!]. For instance if we instead write this:
@racketblock[
(add-transitive-constraint! my-csp < '(a b c d))
]
The constraint is applied between every sequential pair, so the result is equivalent to:
@racketblock[
(add-constraint! my-csp < '(a b))
(add-constraint! my-csp < '(b c))
(add-constraint! my-csp < '(c d))
]
Same truth in half the constraints.
}
@defproc[(make-var-names
[prefix string?]
[vals (listof any/c)]
@ -394,9 +440,9 @@ Helper function to generate mass quantities of variable names. The @racket[_pref
(make-var-names "foo" (range 6) "bar")
(make-var-names "col" (range 10))
]
}
@defproc[(solve
[prob csp?] )
(or/c #false (listof (cons/c symbol? any/c)))]{
@ -410,8 +456,8 @@ Return a solution for the CSP, or @racket[#false] if no solution exists.
Return all the solutions for the CSP. If there are none, returns @racket[null]. The optional @racket[_count] argument returns a certain number of solutions (or fewer, if not that many solutions exist)
}
@defform[(in-solutions prob)]{
Iterator form for use with @racket[for] loops that incrementally returns solutions to @racket[_prob].
@defform[(in-solutions prob count)]{
Iterator form for use with @racket[for] loops that incrementally returns solutions to @racket[_prob], up to a maximum of @racket[_count].
}
@ -445,12 +491,12 @@ Next variable that the CSP solver will attempt to assign a value to. If @racket[
Procedure that orders the remaining values in a domain. Default is @racket[#false], which means that the domain values are tried in their original order. If bad values are likely to be clustered together, it can be worth trying @racket[shuffle] for this parameter, which randomizes which value gets chosen next. Shuffling is also helpful in CSPs where all the variable values must be different (because otherwise, the values for every variable are tried in the same order, which means that the search space is front-loaded with failure).
}
@defparam[current-inference val (or/c #false procedure?) #:value #f]{
Current inference rule used by the solver. If @racket[#false], solver uses @racket[forward-check].
@defparam[current-inference val (or/c #false procedure?) #:value forward-check]{
Current inference rule used by the solver. If @racket[#false], solver uses @racket[no-inference]. Default is @racket[forward-check].
}
@defparam[current-solver val (or/c #false procedure?) #:value #f]{
Current solver algorithm used to solve the CSP. If @racket[#false], CSP will use @racket[backtracking-solver].
@defparam[current-solver val procedure? #:value backtracking-solver]{
Current solver algorithm used to solve the CSP. Default is @racket[backtracking-solver].
}
@defparam[current-decompose val (or/c #false procedure?) #:value #t]{

Loading…
Cancel
Save