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

298 lines
12 KiB
Racket

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#lang debug racket
(require racket/generator sugar/debug)
(struct $csp (variables domains neighbors constraints initial curr_domains nassigns) #:transparent #:mutable)
(define assignment? hash?)
(define variable? symbol?)
(define removal? (cons/c variable? any/c))
(define current-removals (make-parameter empty))
(define/contract (make-csp variables domains neighbors constraints)
((listof variable?) hash? hash? procedure? . -> . $csp?)
($csp variables domains neighbors constraints null #f 0))
(define/contract (assign csp var val assignment)
($csp? variable? any/c assignment? . -> . void?)
;; 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)
($csp? variable? assignment? . -> . void?)
;; 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)
($csp? variable? any/c assignment? . -> . number?)
;; Return the number of conflicts var=val has with other variables."""
;; Subclasses may implement this more efficiently
(for/sum ([v (in-list (hash-ref ($csp-neighbors csp) var))]
#:when (hash-has-key? assignment v))
(if (($csp-constraints csp) var val v (hash-ref assignment v)) 0 1)))
(define (display csp assignment)
(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
(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.)
(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)))
(define/contract (suppose csp var value)
($csp? variable? any/c . -> . (listof removal?))
;; Start accumulating inferences from assuming var=value
(support_pruning csp)
(define removals
(for/list ([a (hash-ref ($csp-curr_domains csp) var)]
#:when (not (equal? a value)))
(cons var a)))
(hash-set! ($csp-curr_domains csp) var (list value))
removals)
;; todo: update uses of `prune` to be functional on removals
(define/contract (prune csp var value)
($csp? variable? any/c . -> . void?)
;; Rule out var=value
(hash-update! ($csp-curr_domains csp) var (λ (vals) (remove value vals)))
(current-removals (append (current-removals) (list (cons var value)))))
(define/contract (choices csp var)
($csp? variable? . -> . (listof any/c))
;; 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)
($csp? . -> . assignment?)
;; Return the partial assignment implied by the current inferences.
(support_pruning csp)
(define assignment (make-hasheq))
(for ([v (in-list ($csp-variables csp))])
(match (hash-ref ($csp-curr_domains csp) v)
[(list one-value) (hash-set! assignment v one-value)]
[else #f]))
assignment)
(define/contract (restore csp)
($csp? . -> . void?)
;; Undo a supposition and all inferences from it.
(for ([removal (in-list (current-removals))])
(match removal
[(cons B b) (hash-update! ($csp-curr_domains csp) B
(λ (vals) (append vals (list b))))])))
;; ______________________________________________________________________________
;; CSP Backtracking Search
;; Variable ordering
(define/contract (first_unassigned_variable assignment csp)
(assignment? $csp? . -> . (or/c #false variable?))
;; The default variable order.
(for/first ([var (in-list ($csp-variables csp))]
#:unless (hash-has-key? assignment var))
var))
(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)
(length (hash-ref ($csp-curr_domains csp) var))
;; 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))]
#:unless (hash-has-key? assignment var))
($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))))))))
;; Value ordering
(define/contract (unordered_domain_values var assignment csp)
(variable? assignment? $csp? . -> . (listof any/c))
;; The default value order.
(choices csp var))
(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))))
;; Inference
(define/contract (no_inference csp var value assignment)
($csp? variable? any/c assignment? . -> . boolean?)
#true)
(define/contract (forward_checking csp var value assignment)
($csp? variable? any/c assignment? . -> . boolean?)
;; Prune neighbor values inconsistent with var=value.
(support_pruning csp) ;; necessary to set up curr_domains
#R var #R value
(for/and ([B (in-list (hash-ref ($csp-neighbors csp) var))]
#:unless (hash-has-key? assignment B))
(report B 'pruning-var)
(for ([b (in-list (hash-ref ($csp-curr_domains csp) B))]
#:unless (($csp-constraints csp) var value B b))
(report b 'pruning-val)
(prune csp B b))
(not (empty? #R (hash-ref ($csp-curr_domains csp) B)))))
(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))
(define/contract (backtracking_search csp
[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)
(parameterize ([current-removals (suppose csp var val)])
(when (inference csp var val assignment)
(backtrack assignment))
(restore csp)))
(unassign csp var assignment)]))))
(define current-reset (make-parameter #t))
(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)))
(begin0
(match (for/list ([solution (in-producer (solver csp) (void))]
[idx (in-range solution-limit)])
(finish-proc solution))
[(? pair? solutions) solutions]
[else #false])
(when (current-reset)
(set-$csp-curr_domains! csp #f))))
(define/contract (solve csp [solver backtracking_search] [finish-proc values])
(($csp?) (procedure? procedure?) . ->* . any/c)
(match (solve* csp solver finish-proc #:count 1)
[(list solution) solution]
[else #false]))
(require rackunit)
(define vs '(wa nsw t q nt v sa))
(define ds (for/hash ([k vs])
(values k '(red green blue))))
(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)))))
(check-true ($csp? csp))
(define a (make-hasheq))
(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)))
(check-equal? (suppose csp 'wa 'red) '((wa . green) (wa . blue)))
(check-equal?
(hash-ref ($csp-curr_domains csp) 'wa) '(red))
#;(check-equal? (prune csp 'v 'red) '((v . red)))
#;(check-equal? (choices csp 'v) '(green blue))
(check-equal? (choices csp 'wa) '(red))
(check-equal? (infer_assignment csp)
(make-hasheq '((wa . red))))
#;(check-equal? (suppose csp 'v 'blue) '((v . green)))
#;(check-equal? (infer_assignment csp)
(make-hasheq '((v . blue) (wa . red))))
#|
(restore csp '((wa . green)))
(check-equal? (infer_assignment csp)
(make-hasheq '((v . blue))))
(restore csp '((v . blue)))
(check-equal? (infer_assignment csp) (make-hasheq))
|#
(check-equal? (first_unassigned_variable (hash) csp) 'wa)
#;(check-equal? (unordered_domain_values 'wa (hash) csp) '(red green))
(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))))
(check-equal? (length (solve* csp)) 18)
(check-equal? (suppose csp 'nsw 'red) '((nsw . green) (nsw . blue)))
(check-equal? (solve csp)
(make-hasheq '((nsw . red) (nt . red) (q . green) (sa . blue) (t . blue) (v . green) (wa . green))))
(check-equal? (suppose csp 'nsw 'red) '((nsw . green) (nsw . blue)))
(check-equal? (length (solve* csp)) 6)
(parameterize ([current-select-variable mrv]
[current-shuffle #f])
(check-equal?
(solve csp)
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red)))))
(parameterize ([current-order-values lcv])
(check-equal?
(solve csp)
(make-hasheq '((nsw . green) (nt . green) (q . red) (sa . blue) (t . blue) (v . red) (wa . red)))))
#;(parameterize ([current-inference forward_checking])
(forward_checking csp 'sa 'blue (make-hasheq) 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))))))
(set-$csp-curr_domains! csp #f)
(parameterize ([current-inference forward_checking]
[current-reset #f])
(support_pruning csp)
(solve csp))