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/hacs.rkt

187 lines
7.6 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)
(provide (all-defined-out))
(struct $csp ([vars #:mutable]
[constraints #:mutable]) #:transparent)
(struct $constraint (names proc) #:transparent)
(struct $var (name domain past) #:transparent)
(define (+var name vals [past null])
($var name vals past))
(define $var-name? symbol?)
(struct $avar $var () #:transparent)
(define (+avar name vals [past null])
($avar name vals past))
(struct inconsistency-signal (csp) #:transparent)
(struct $backtrack (names) #:transparent)
(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-shuffle (make-parameter #t))
(define/contract (check-name-in-csp! caller csp name)
(symbol? $csp? $var-name? . -> . void?)
(define names (map $var-name ($csp-vars csp)))
(unless (memq name names)
(raise-argument-error caller (format "one of these existing csp var names: ~v" names) name)))
(define/contract ($csp-var csp name)
($csp? $var-name? . -> . $var?)
(check-name-in-csp! '$csp-var csp name)
(for/first ([var (in-list ($csp-vars csp))]
#:when (eq? name ($var-name var)))
var))
(define/contract ($csp-vals csp name)
($csp? $var-name? . -> . (listof any/c))
(check-name-in-csp! '$csp-vals csp name)
($var-domain ($csp-var csp name)))
(define order-domain-values values)
(define/contract (assign-val csp name val)
($csp? $var-name? any/c . -> . $csp?)
($csp
(for/list ([var ($csp-vars csp)])
(if (eq? name ($var-name var))
(+avar name (list val) ($var-past var))
var))
($csp-constraints csp)))
(define/contract (update-conflicts csp name conflicts)
($csp? $var-name? (listof $var-name?) . -> . $csp?)
($csp
(for/list ([var ($csp-vars csp)])
(match var
[($var (? (λ (x) (eq? x name))) vals past)
(+avar name vals past conflicts)]
[else var]))
($csp-constraints csp)))
(define (unassigned-vars csp)
(for/list ([var (in-list ($csp-vars csp))]
#:unless ($avar? var))
var))
(define/contract (first-unassigned-variable csp)
($csp? . -> . (or/c #false (and/c $var? (not/c $avar?))))
(match (unassigned-vars csp)
[(? empty?) #false]
[xs (first xs)]))
(define/contract (argmin-random-tie proc xs)
(procedure? (non-empty-listof any/c) . -> . any/c)
(define ordered-xs (sort xs < #:key proc))
(first ((if (current-shuffle) shuffle values)
(takef ordered-xs (λ (x) (= (proc (car ordered-xs)) (proc x)))))))
(define/contract (minimum-remaining-values csp)
($csp? . -> . (or/c #false (and/c $var? (not/c $avar?))))
(struct $mrv-rec (var num) #:transparent)
(match (unassigned-vars csp)
[(? empty?) #false]
[xs (argmin-random-tie (λ (var) (length ($var-domain var))) xs)]))
(define first-domain-value values)
(define (no-inference csp name) csp)
(define/contract (relating constraints names)
((listof $constraint?) (listof $var-name?) . -> . (listof $constraint?))
(for*/list ([constraint (in-list constraints)]
[cnames (in-value ($constraint-names constraint))]
#:when (for/and ([name (in-list names)])
(memq name cnames)))
constraint))
(define/contract (forward-check csp aname)
($csp? $var-name? . -> . $csp?)
(define aval (first ($csp-vals csp aname)))
(define (check-var var)
(match var
[(? $avar?) var]
[($var name vals past)
(match (($csp-constraints csp) . relating . (list aname name))
[(? empty?) var]
[constraints
(define new-vals
(for/list ([val (in-list vals)]
#:when (for/and ([constraint (in-list constraints)])
(let ([proc ($constraint-proc constraint)])
(if (eq? name (first ($constraint-names constraint)))
(proc val aval)
(proc aval val)))))
val))
(+var name new-vals (cons aname past))])]))
(define checked-vars (map check-var ($csp-vars csp)))
;; conflict-set will be empty if there are no empty domains
(define conflict-set (for*/list ([var (in-list checked-vars)]
#:when (empty? ($var-domain var))
[name (in-list ($var-past var))])
name))
;; 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.
;; If we just bail out at the first conflict, we may backjump too far based on its history
;; (and thereby miss parts of the search tree)
(when (pair? conflict-set)
(raise ($backtrack conflict-set)))
($csp checked-vars ($csp-constraints csp)))
(define/contract (backtracking-solver
csp
#: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) no-inference)])
(($csp?) (#:select-variable procedure? #:order-values procedure? #:inference procedure?) . ->* . generator?)
(generator ()
(let loop ([csp csp])
(match (select-unassigned-variable csp)
[#false (yield csp)]
[($var name domain _)
(define (wants-backtrack? exn)
(and ($backtrack? exn) (memq name ($backtrack-names exn))))
(for/fold ([conflicts null]
#:result (void))
([val (in-list (order-domain-values domain))])
(with-handlers ([wants-backtrack?
(λ (bt) (append conflicts (remq name ($backtrack-names bt))))])
(define csp-with-assignment (assign-val csp name val))
(loop (inference csp-with-assignment name)))
conflicts)]))))
(define/contract (solution-consistent? solution)
($csp? . -> . boolean?)
(for/and ([c (in-list ($csp-constraints solution))])
(apply ($constraint-proc c) (for*/list ([name (in-list ($constraint-names c))]
[var (in-list ($csp-vars solution))]
#:when (eq? name ($var-name var)))
(first ($var-domain var))))))
(define/contract (solve* csp
#:finish-proc [finish-proc $csp-vars]
#:solver [solver (or (current-solver) backtracking-solver)]
#:count [max-solutions +inf.0])
(($csp?) (#:finish-proc procedure? #:solver procedure? #:count integer?) . ->* . (listof any/c))
(for/list ([solution (in-producer (solver csp) (void))]
[idx (in-range max-solutions)])
(unless (solution-consistent? solution)
(raise (list 'wtf solution)))
(finish-proc solution)))
(define/contract (solve csp
#:finish-proc [finish-proc $csp-vars]
#:solver [solver (or (current-solver) backtracking-solver)])
(($csp?) (#:finish-proc procedure? #:solver procedure?) . ->* . (or/c #false any/c))
(match (solve* csp #:finish-proc finish-proc #:solver solver #:count 1)
[(list solution) solution]
[else #false]))
(define (<> a b) (not (= a b)))
(define (neq? a b) (not (eq? a b)))