You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/csp/aima.rkt

316 lines
13 KiB
Racket

6 years ago
#lang debug racket
6 years ago
(require racket/generator sugar/debug)
6 years ago
(struct $csp (variables domains neighbors constraints initial curr_domains nassigns) #:transparent #:mutable)
6 years ago
(define assignment? hash?)
(define variable? symbol?)
(define removal? (cons/c variable? any/c))
6 years ago
(define/contract (make-csp variables domains neighbors constraints)
6 years ago
((listof variable?) hash? hash? procedure? . -> . $csp?)
($csp variables domains neighbors constraints null #f 0))
6 years ago
6 years ago
(define/contract (curr_domain csp var)
($csp? variable? . -> . (listof any/c))
(hash-ref ($csp-curr_domains csp) var))
(define/contract (neighbors csp var)
($csp? variable? . -> . (listof variable?))
(hash-ref ($csp-neighbors csp) var))
(define/contract (assigns? assignment var)
(assignment? variable? . -> . boolean?)
(hash-has-key? assignment var))
6 years ago
(define/contract (reset-nassigns! csp)
($csp? . -> . void?)
(set-$csp-nassigns! csp 0))
6 years ago
(define/contract (assign csp var val assignment)
6 years ago
($csp? variable? any/c assignment? . -> . void?)
6 years ago
;; Add {var: val} to assignment; Discard the old value if any.
(hash-set! assignment var val)
(set-$csp-nassigns! csp (add1 ($csp-nassigns csp))))
(define/contract (unassign csp var assignment)
6 years ago
($csp? variable? assignment? . -> . void?)
6 years ago
;; Remove {var: val} from assignment.
;; DO NOT call this if you are changing a variable to a new value;
;; just call assign for that.
(hash-remove! assignment var))
(define/contract (nconflicts csp var val assignment)
6 years ago
($csp? variable? any/c assignment? . -> . number?)
6 years ago
;; Return the number of conflicts var=val has with other variables."""
;; Subclasses may implement this more efficiently
6 years ago
(for/sum ([v (in-list (neighbors csp var))]
#:when (assignment . assigns? . v))
6 years ago
(if (($csp-constraints csp) var val v (hash-ref assignment v)) 0 1)))
6 years ago
(define (display csp assignment)
6 years ago
(displayln csp))
(define/contract (all-variables-assigned? csp assignment)
($csp? assignment? . -> . boolean?)
(= (length (hash-keys assignment)) (length ($csp-variables csp))))
(define/contract (goal_test csp state)
($csp? assignment? . -> . boolean?)
;; The goal is to assign all variables, with all constraints satisfied.
(define assignment state)
(and (all-variables-assigned? csp assignment)
(for/and ([variable ($csp-variables csp)])
(zero? (nconflicts csp variable (hash-ref assignment variable) assignment)))))
;; These are for constraint propagation
6 years ago
(define/contract (support_pruning csp)
($csp? . -> . void?)
;; Make sure we can prune values from domains. (We want to pay
;; for this only if we use it.)
6 years ago
(unless ($csp-curr_domains csp)
(define h (make-hasheq))
(for ([v ($csp-variables csp)])
(hash-set! h v (hash-ref ($csp-domains csp) v)))
(set-$csp-curr_domains! csp h)))
6 years ago
(define/contract (suppose csp var value)
6 years ago
($csp? variable? any/c . -> . (box/c (listof removal?)))
6 years ago
;; Start accumulating inferences from assuming var=value
(support_pruning csp)
6 years ago
(begin0
6 years ago
(box (for/list ([val (in-list (curr_domain csp var))]
#:when (not (equal? val value)))
(cons var val)))
6 years ago
(hash-set! ($csp-curr_domains csp) var (list value))))
6 years ago
6 years ago
;; todo: update uses of `prune` to be functional on removals
(define/contract (prune csp var value removals)
6 years ago
($csp? variable? any/c (or/c #f (box/c (listof removal?))) . -> . (box/c (listof removal?)))
6 years ago
;; Rule out var=value
6 years ago
(hash-update! ($csp-curr_domains csp) var (λ (vals) (remove value vals)))
6 years ago
(and removals
(set-box! removals (append (unbox removals) (list (cons var value))))
removals))
6 years ago
(define/contract (choices csp var)
6 years ago
($csp? variable? . -> . (listof any/c))
6 years ago
;; Return all values for var that aren't currently ruled out.
(hash-ref (or ($csp-curr_domains csp) ($csp-domains csp)) var))
(define/contract (infer_assignment csp)
6 years ago
($csp? . -> . assignment?)
6 years ago
;; Return the partial assignment implied by the current inferences.
(support_pruning csp)
6 years ago
(define assignment (make-hasheq))
(for ([v (in-list ($csp-variables csp))])
6 years ago
(match (curr_domain csp v)
6 years ago
[(list one-value) (hash-set! assignment v one-value)]
[else #f]))
assignment)
6 years ago
(define/contract (restore csp removals)
6 years ago
($csp? (box/c (listof removal?)) . -> . void?)
6 years ago
;; Undo a supposition and all inferences from it.
6 years ago
(for ([removal (in-list (unbox removals))])
6 years ago
(match removal
[(cons B b) (hash-update! ($csp-curr_domains csp) B
(λ (vals) (append vals (list b))))])))
6 years ago
;; ______________________________________________________________________________
;; CSP Backtracking Search
;; Variable ordering
(define/contract (first_unassigned_variable assignment csp)
6 years ago
(assignment? $csp? . -> . (or/c #false variable?))
6 years ago
;; The default variable order.
6 years ago
(for/first ([var (in-list ($csp-variables csp))]
6 years ago
#:unless (assignment . assigns? . var))
6 years ago
var))
6 years ago
(define current-shuffle (make-parameter #t))
(define/contract (mrv assignment csp)
(assignment? $csp? . -> . any/c)
;; Minimum-remaining-values heuristic.
;; with random tiebreaker.
(define (num_legal_values var)
(if ($csp-curr_domains csp)
6 years ago
(length (curr_domain csp var))
6 years ago
;; todo: is this the same as python `count`?
(for/sum ([val (in-list (hash-ref ($csp-domains csp) var))]
#:when (zero? (nconflicts csp var val assignment)))
1)))
(struct $mrv-rec (var num) #:transparent)
(define recs (sort
(for/list ([var (in-list ($csp-variables csp))]
6 years ago
#:unless (assignment . assigns? . var))
6 years ago
($mrv-rec var (num_legal_values var)))
< #:key $mrv-rec-num))
(first ((if (current-shuffle) shuffle values) (map $mrv-rec-var (takef recs (λ (rec) (= ($mrv-rec-num (first recs))
($mrv-rec-num rec))))))))
6 years ago
;; Value ordering
(define/contract (unordered_domain_values var assignment csp)
6 years ago
(variable? assignment? $csp? . -> . (listof any/c))
6 years ago
;; The default value order.
(choices csp var))
6 years ago
(define/contract (lcv var assignment csp)
(variable? assignment? $csp? . -> . (listof any/c))
;; Least-constraining-values heuristic.
(sort (choices csp var) < #:key (λ (val) (nconflicts csp var val assignment))))
6 years ago
;; Inference
(define/contract (no_inference csp var value assignment removals)
6 years ago
($csp? variable? any/c assignment? (box/c (listof removal?)) . -> . boolean?)
6 years ago
#true)
(define/contract (forward_checking csp var value assignment removals)
6 years ago
($csp? variable? any/c assignment? (box/c (listof removal?)) . -> . boolean?)
6 years ago
;; Prune neighbor values inconsistent with var=value.
(support_pruning csp) ;; necessary to set up curr_domains
6 years ago
(for/and ([B (in-list (neighbors csp var))]
#:unless (assignment . assigns? . B))
(for ([b (in-list (curr_domain csp B))]
6 years ago
#:unless (($csp-constraints csp) var value B b))
(prune csp B b removals))
6 years ago
(not (empty? (curr_domain csp B)))))
6 years ago
(define current-select-variable (make-parameter first_unassigned_variable))
(define current-order-values (make-parameter unordered_domain_values))
(define current-inference (make-parameter no_inference))
6 years ago
(define/contract (backtracking_search csp
6 years ago
[select_unassigned_variable (current-select-variable)]
[order_domain_values (current-order-values)]
[inference (current-inference)])
(($csp?) (procedure? procedure? procedure?) . ->* . generator?)
(generator ()
(let backtrack ([assignment (make-hasheq)])
(cond
[(all-variables-assigned? csp assignment)
(unless (goal_test csp assignment) (error 'whut))
(yield (hash-copy assignment))]
[else
(define var (select_unassigned_variable assignment csp))
(for ([val (in-list (order_domain_values var assignment csp))]
#:when (zero? (nconflicts csp var val assignment)))
(assign csp var val assignment)
(define removals (suppose csp var val))
(when (inference csp var val assignment removals)
(backtrack assignment))
(restore csp removals))
6 years ago
(unassign csp var assignment)]))))
6 years ago
(define current-reset (make-parameter #t))
6 years ago
(define/contract (solve* csp [solver backtracking_search] [finish-proc values]
#:count [solution-limit +inf.0])
(($csp?) (procedure? procedure? #:count integer?) . ->* . (or/c #f (non-empty-listof any/c)))
6 years ago
(begin0
(match (for/list ([solution (in-producer (solver csp) (void))]
[idx (in-range solution-limit)])
(finish-proc solution))
[(? pair? solutions) solutions]
[else #false])
6 years ago
(when (current-reset)
6 years ago
(set-$csp-curr_domains! csp #f))))
6 years ago
(define/contract (solve csp [solver backtracking_search] [finish-proc values])
(($csp?) (procedure? procedure?) . ->* . any/c)
6 years ago
(match (solve* csp solver finish-proc #:count 1)
[(list solution) solution]
6 years ago
[else #false]))
6 years ago
(require rackunit)
(define vs '(wa nsw t q nt v sa))
(define ds (for/hash ([k vs])
(values k '(red green blue))))
6 years ago
(define ns (for*/hash ([(i ns) (in-dict
'((wa nt sa)
(nt wa sa q)
(q nt sa nsw)
(nsw q sa v)
(v sa nsw)
(sa wa nt q nsw v)
(t)))])
(values i ns)))
(define csp (make-csp vs ds ns (λ (A a B b) (not (equal? a b)))))
6 years ago
(check-true ($csp? csp))
6 years ago
(define a (make-hasheq))
6 years ago
(assign csp 'key 42 a)
(check-equal? (hash-ref a 'key) 42)
(unassign csp 'key a)
(check-exn exn:fail? (λ () (hash-ref a 'key)))
(check-equal? 0 (nconflicts csp 'wa 'red (hasheq 'wa 42)))
(support_pruning csp)
(check-true (hash? ($csp-curr_domains csp)))
6 years ago
(check-equal? (suppose csp 'wa 'red) '#&((wa . green) (wa . blue)))
6 years ago
(check-equal? (curr_domain csp 'wa) '(red))
6 years ago
6 years ago
(check-equal? (prune csp 'v 'red (box empty)) '#&((v . red)))
6 years ago
(check-equal? (choices csp 'v) '(green blue))
6 years ago
(check-equal? (choices csp 'wa) '(red))
(check-equal? (infer_assignment csp)
6 years ago
(make-hasheq '((wa . red))))
6 years ago
(check-equal? (suppose csp 'v 'blue) '#&((v . green)))
(check-equal? (infer_assignment csp)
6 years ago
(make-hasheq '((v . blue) (wa . red))))
6 years ago
(restore csp '#&((wa . green)))
6 years ago
(check-equal? (infer_assignment csp)
6 years ago
(make-hasheq '((v . blue))))
6 years ago
(restore csp '#&((v . blue)))
6 years ago
(check-equal? (infer_assignment csp) (make-hasheq))
6 years ago
(check-equal? (first_unassigned_variable (hash) csp) 'wa)
(check-equal? (unordered_domain_values 'wa (hash) csp) '(red green))
6 years ago
6 years ago
(set-$csp-curr_domains! csp #f) ; reset current domains
(check-equal? (solve csp)
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red))))
6 years ago
(check-equal? (begin0 ($csp-nassigns csp) (reset-nassigns! csp)) 40)
6 years ago
(check-equal? (length (solve* csp)) 18)
6 years ago
6 years ago
(check-equal? (suppose csp 'nsw 'red) '#&((nsw . green) (nsw . blue)))
6 years ago
(check-equal? (solve csp)
(make-hasheq '((nsw . red) (nt . red) (q . green) (sa . blue) (t . blue) (v . green) (wa . green))))
6 years ago
(check-equal? ($csp-nassigns csp) 368)
6 years ago
6 years ago
(check-equal? (suppose csp 'nsw 'red) '#&((nsw . green) (nsw . blue)))
6 years ago
(check-equal? (length (solve* csp)) 6)
6 years ago
(check-equal? (begin0 ($csp-nassigns csp) (reset-nassigns! csp)) 479)
6 years ago
(parameterize ([current-select-variable mrv]
[current-shuffle #f])
(check-equal?
(solve csp)
6 years ago
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red))))
(check-equal? (begin0 ($csp-nassigns csp) (reset-nassigns! csp)) 39))
6 years ago
(parameterize ([current-order-values lcv])
(check-equal?
(solve csp)
6 years ago
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red))))
(check-equal? (begin0 ($csp-nassigns csp) (reset-nassigns! csp)) 39))
6 years ago
6 years ago
(parameterize ([current-inference forward_checking])
6 years ago
(forward_checking csp 'sa 'blue (make-hasheq) (box null))
(check-equal? ($csp-curr_domains csp)
(make-hasheq '((nsw . (red green)) (nt . (red green)) (q . (red green)) (sa . (red green blue)) (t . (red green blue)) (v . (red green)) (wa . (red green))))))
6 years ago
6 years ago
(set-$csp-curr_domains! csp #f)
(parameterize ([current-inference forward_checking]
[current-reset #f])
6 years ago
(check-equal?
(solve csp)
6 years ago
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red))))
(check-equal? (begin0 ($csp-nassigns csp) (reset-nassigns! csp)) 25))
6 years ago