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

228 lines
8.6 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
(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 (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)))
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 . -> . (listof removal?))
6 years ago
;; 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)
6 years ago
;; todo: update uses of `prune` to be functional on removals
6 years ago
(define/contract (prune csp var value removals)
6 years ago
($csp? variable? any/c (or/c #f (listof removal?)) . -> . (listof removal?))
6 years ago
;; Rule out var=value
6 years ago
(hash-update! ($csp-curr_domains csp) var (λ (vals) (remove value vals)))
(and removals (append removals (list (cons var value)))))
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))])
(match (hash-ref ($csp-curr_domains csp) v)
[(list one-value) (hash-set! assignment v one-value)]
[else #f]))
assignment)
6 years ago
(define/contract (restore csp removals)
6 years ago
($csp? (listof removal?) . -> . void?)
6 years ago
;; Undo a supposition and all inferences from it.
6 years ago
(for ([removal (in-list removals)])
(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))]
#:unless (hash-has-key? assignment var))
6 years ago
var))
;; 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))
;; Inference
(define/contract (no_inference csp var value assignment removals)
6 years ago
($csp? variable? any/c assignment? (listof removal?) . -> . boolean?)
6 years ago
#true)
(define/contract (backtracking_search csp
[select_unassigned_variable first_unassigned_variable]
[order_domain_values unordered_domain_values]
[inference no_inference])
6 years ago
(($csp?) (procedure? procedure? procedure?) . ->* . (or/c #f assignment?))
(define (backtrack [assignment (make-hasheq)])
;; todo: convert to generator with `yield`
(let/ec return
(when (all-variables-assigned? csp assignment)
(return assignment))
(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)
(define result (backtrack assignment))
(when result
(return result))
(restore csp removals)))
(unassign csp var assignment)
(return #false)))
(define result (backtrack))
(unless (or (false? result) (goal_test csp result))
(error 'whut))
result)
6 years ago
6 years ago
;; todo: make multiple results work
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
(match (for/list ([solution (in-value (solver csp))] ; needs generator here
6 years ago
[idx (in-range solution-limit)])
(finish-proc solution))
[(? pair? solutions) solutions]
[else #f]))
(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]
[else #f]))
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?
(hash-ref ($csp-curr_domains csp) 'wa) '(red))
(check-equal? (prune csp 'v 'red empty) '((v . red)))
(check-equal? (choices csp 'v) '(green blue))
(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)))
(check-equal? (infer_assignment csp)
6 years ago
(make-hasheq '((v . blue))))
(restore csp '((v . blue)))
(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
(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))))
(set-$csp-curr_domains! csp #f)
(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))))