From d418d1ca0c638f7e2eb97f4ad7b45210a264ca1a Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Mon, 22 Oct 2018 16:20:14 -0700 Subject: [PATCH] motion --- csp/{ => csp}/aima-queens.rkt | 0 csp/{ => csp}/aima-smm.rkt | 0 csp/{ => csp}/aima-sum.rkt | 0 csp/{ => csp}/aima.rkt | 0 csp/{ => csp}/csp-test-etc.rkt | 0 csp/{ => csp}/csp-test-problems.rkt | 0 csp/{ => csp}/csp-test.rkt | 0 csp/{ => csp}/csp.rkt | 0 csp/{ => csp}/hacs-map.rkt | 0 csp/{ => csp}/hacs-smm.rkt | 0 csp/csp/hacs-test-workbench.rkt | 30 + csp/{ => csp}/hacs-test.rkt | 0 csp/csp/hacs.rkt | 545 ++++++++++++++++++ csp/{ => csp}/main.rkt | 0 csp/{ => csp}/port/constraint.rkt | 0 csp/{ => csp}/port/domain.rkt | 0 csp/{ => csp}/port/helper.rkt | 0 csp/{ => csp}/port/main.rkt | 0 csp/{ => csp}/port/problem.rkt | 0 csp/{ => csp}/port/solver.rkt | 0 csp/{ => csp}/port/test-classes.rkt | 0 csp/{ => csp}/port/test-einstein.rkt | 0 csp/{ => csp}/port/test-problems.rkt | 0 csp/{ => csp}/port/variable.rkt | 0 .../API Documentation.webloc | 0 csp/{ => csp}/python-constraint/LICENSE | 0 csp/{ => csp}/python-constraint/MANIFEST.in | 0 csp/{ => csp}/python-constraint/PKG-INFO | 0 csp/{ => csp}/python-constraint/README | 0 csp/{ => csp}/python-constraint/constraint.py | 0 .../python-constraint/examples/abc/abc.py | 0 .../python-constraint/examples/coins/coins.py | 0 .../examples/crosswords/crosswords.py | 0 .../examples/crosswords/large.mask | 0 .../examples/crosswords/medium.mask | 0 .../examples/crosswords/python.mask | 0 .../examples/crosswords/small.mask | 0 .../examples/einstein/einstein.py | 0 .../examples/einstein/einstein2.py | 0 .../examples/queens/queens.py | 0 .../python-constraint/examples/rooks/rooks.py | 0 .../examples/studentdesks/studentdesks.py | 0 .../examples/sudoku/sudoku.py | 0 .../examples/wordmath/seisseisdoze.py | 0 .../examples/wordmath/sendmoremoney.py | 0 .../examples/wordmath/twotwofour.py | 0 .../python-constraint/examples/xsum/xsum.py | 0 csp/{ => csp}/python-constraint/setup.cfg | 0 csp/{ => csp}/python-constraint/setup.py | 0 .../python-constraint/testconstraint.py | 0 .../python-constraint/trials/abcd.py | 0 .../python-constraint/trials/coins.py | 0 .../python-constraint/trials/constraint.py | 0 .../python-constraint/trials/crosswords.py | 0 .../python-constraint/trials/einstein.py | 0 .../python-constraint/trials/einstein2.py | 0 .../python-constraint/trials/large.mask | 0 .../python-constraint/trials/medium.mask | 0 .../python-constraint/trials/python.mask | 0 .../python-constraint/trials/queens.py | 0 .../python-constraint/trials/rooks.py | 0 .../python-constraint/trials/seisseisdoze.py | 0 .../python-constraint/trials/sendmoremoney.py | 0 .../python-constraint/trials/small.mask | 0 .../python-constraint/trials/studentdesks.py | 0 .../python-constraint/trials/sudoku.py | 0 .../python-constraint/trials/twotwofour.py | 0 .../python-constraint/trials/xsum.py | 0 csp/csp/scribblings/csp.scrbl | 31 + csp/info.rkt | 7 +- 70 files changed, 609 insertions(+), 4 deletions(-) rename csp/{ => csp}/aima-queens.rkt (100%) rename csp/{ => csp}/aima-smm.rkt (100%) rename csp/{ => csp}/aima-sum.rkt (100%) rename csp/{ => csp}/aima.rkt (100%) rename csp/{ => csp}/csp-test-etc.rkt (100%) rename csp/{ => csp}/csp-test-problems.rkt (100%) rename csp/{ => csp}/csp-test.rkt (100%) rename csp/{ => csp}/csp.rkt (100%) rename csp/{ => csp}/hacs-map.rkt (100%) rename csp/{ => csp}/hacs-smm.rkt (100%) create mode 100644 csp/csp/hacs-test-workbench.rkt rename csp/{ => csp}/hacs-test.rkt (100%) create mode 100644 csp/csp/hacs.rkt rename csp/{ => csp}/main.rkt (100%) rename csp/{ => csp}/port/constraint.rkt (100%) rename csp/{ => csp}/port/domain.rkt (100%) rename csp/{ => csp}/port/helper.rkt (100%) rename csp/{ => csp}/port/main.rkt (100%) rename csp/{ => csp}/port/problem.rkt (100%) rename csp/{ => csp}/port/solver.rkt (100%) rename csp/{ => csp}/port/test-classes.rkt (100%) rename csp/{ => csp}/port/test-einstein.rkt (100%) rename csp/{ => csp}/port/test-problems.rkt (100%) rename csp/{ => csp}/port/variable.rkt (100%) rename csp/{ => csp}/python-constraint/API Documentation.webloc (100%) rename csp/{ => csp}/python-constraint/LICENSE (100%) rename csp/{ => csp}/python-constraint/MANIFEST.in (100%) rename csp/{ => csp}/python-constraint/PKG-INFO (100%) rename csp/{ => csp}/python-constraint/README (100%) rename csp/{ => csp}/python-constraint/constraint.py (100%) rename csp/{ => csp}/python-constraint/examples/abc/abc.py (100%) rename csp/{ => csp}/python-constraint/examples/coins/coins.py (100%) rename csp/{ => csp}/python-constraint/examples/crosswords/crosswords.py (100%) rename csp/{ => csp}/python-constraint/examples/crosswords/large.mask (100%) rename csp/{ => csp}/python-constraint/examples/crosswords/medium.mask (100%) rename csp/{ => csp}/python-constraint/examples/crosswords/python.mask (100%) rename csp/{ => csp}/python-constraint/examples/crosswords/small.mask (100%) rename csp/{ => csp}/python-constraint/examples/einstein/einstein.py (100%) rename csp/{ => csp}/python-constraint/examples/einstein/einstein2.py (100%) rename csp/{ => csp}/python-constraint/examples/queens/queens.py (100%) rename csp/{ => csp}/python-constraint/examples/rooks/rooks.py (100%) rename csp/{ => csp}/python-constraint/examples/studentdesks/studentdesks.py (100%) rename csp/{ => csp}/python-constraint/examples/sudoku/sudoku.py (100%) rename csp/{ => csp}/python-constraint/examples/wordmath/seisseisdoze.py (100%) rename csp/{ => csp}/python-constraint/examples/wordmath/sendmoremoney.py (100%) rename csp/{ => csp}/python-constraint/examples/wordmath/twotwofour.py (100%) rename csp/{ => csp}/python-constraint/examples/xsum/xsum.py (100%) rename csp/{ => csp}/python-constraint/setup.cfg (100%) rename csp/{ => csp}/python-constraint/setup.py (100%) rename csp/{ => csp}/python-constraint/testconstraint.py (100%) rename csp/{ => csp}/python-constraint/trials/abcd.py (100%) rename csp/{ => csp}/python-constraint/trials/coins.py (100%) rename csp/{ => csp}/python-constraint/trials/constraint.py (100%) rename csp/{ => csp}/python-constraint/trials/crosswords.py (100%) rename csp/{ => csp}/python-constraint/trials/einstein.py (100%) rename csp/{ => csp}/python-constraint/trials/einstein2.py (100%) rename csp/{ => csp}/python-constraint/trials/large.mask (100%) rename csp/{ => csp}/python-constraint/trials/medium.mask (100%) rename csp/{ => csp}/python-constraint/trials/python.mask (100%) rename csp/{ => csp}/python-constraint/trials/queens.py (100%) rename csp/{ => csp}/python-constraint/trials/rooks.py (100%) rename csp/{ => csp}/python-constraint/trials/seisseisdoze.py (100%) rename csp/{ => csp}/python-constraint/trials/sendmoremoney.py (100%) rename csp/{ => csp}/python-constraint/trials/small.mask (100%) rename csp/{ => csp}/python-constraint/trials/studentdesks.py (100%) rename csp/{ => csp}/python-constraint/trials/sudoku.py (100%) rename csp/{ => csp}/python-constraint/trials/twotwofour.py (100%) rename csp/{ => csp}/python-constraint/trials/xsum.py (100%) create mode 100644 csp/csp/scribblings/csp.scrbl diff --git a/csp/aima-queens.rkt b/csp/csp/aima-queens.rkt similarity index 100% rename from csp/aima-queens.rkt rename to csp/csp/aima-queens.rkt diff --git a/csp/aima-smm.rkt b/csp/csp/aima-smm.rkt similarity index 100% rename from csp/aima-smm.rkt rename to csp/csp/aima-smm.rkt diff --git a/csp/aima-sum.rkt b/csp/csp/aima-sum.rkt similarity index 100% rename from csp/aima-sum.rkt rename to csp/csp/aima-sum.rkt diff --git a/csp/aima.rkt b/csp/csp/aima.rkt similarity index 100% rename from csp/aima.rkt rename to csp/csp/aima.rkt diff --git a/csp/csp-test-etc.rkt b/csp/csp/csp-test-etc.rkt similarity index 100% rename from csp/csp-test-etc.rkt rename to csp/csp/csp-test-etc.rkt diff --git a/csp/csp-test-problems.rkt b/csp/csp/csp-test-problems.rkt similarity index 100% rename from csp/csp-test-problems.rkt rename to csp/csp/csp-test-problems.rkt diff --git a/csp/csp-test.rkt b/csp/csp/csp-test.rkt similarity index 100% rename from csp/csp-test.rkt rename to csp/csp/csp-test.rkt diff --git a/csp/csp.rkt b/csp/csp/csp.rkt similarity index 100% rename from csp/csp.rkt rename to csp/csp/csp.rkt diff --git a/csp/hacs-map.rkt b/csp/csp/hacs-map.rkt similarity index 100% rename from csp/hacs-map.rkt rename to csp/csp/hacs-map.rkt diff --git a/csp/hacs-smm.rkt b/csp/csp/hacs-smm.rkt similarity index 100% rename from csp/hacs-smm.rkt rename to csp/csp/hacs-smm.rkt diff --git a/csp/csp/hacs-test-workbench.rkt b/csp/csp/hacs-test-workbench.rkt new file mode 100644 index 00000000..df69806d --- /dev/null +++ b/csp/csp/hacs-test-workbench.rkt @@ -0,0 +1,30 @@ +#lang debug racket +(require sugar/debug "hacs.rkt") + +(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 queens (make-csp)) +(define qs (for/list ([q board-size]) (string->symbol (format "q~a" q)))) +(define rows (range (length qs))) +(add-vars! queens qs rows) +(define (q-col q) (string->number (string-trim (symbol->string q) "q"))) +(for* ([qs (in-combinations qs 2)]) + (match-define (list qa qb) qs) + (match-define (list qa-col qb-col) (map q-col qs)) + (add-constraint! queens + (λ (qa-row qb-row) + (not (= (abs (- qa-row qb-row)) (abs (- (q-col qa) (q-col qb)))))) ; same diag? + (list qa qb)) + (add-constraint! queens (negate =) (list qa qb))) + +(time-avg 10 (solve queens)) +(parameterize ([current-solver min-conflicts-solver]) + (time-named (solve queens))) \ No newline at end of file diff --git a/csp/hacs-test.rkt b/csp/csp/hacs-test.rkt similarity index 100% rename from csp/hacs-test.rkt rename to csp/csp/hacs-test.rkt diff --git a/csp/csp/hacs.rkt b/csp/csp/hacs.rkt new file mode 100644 index 00000000..23a2cc84 --- /dev/null +++ b/csp/csp/hacs.rkt @@ -0,0 +1,545 @@ +#lang debug racket +(require racket/generator graph sugar/debug) +(provide (all-defined-out)) + +(define-syntax when-debug + (let () + (define debug #f) + (if debug + (make-rename-transformer #'begin) + (λ (stx) (syntax-case stx () + [(_ . rest) #'(void)]))))) + +(define-syntax-rule (in-cartesian x) + (in-generator (let ([argss x]) + (let loop ([argss argss][acc empty]) + (if (null? argss) + (yield (reverse acc)) + (for ([arg (in-list (car argss))]) + (loop (cdr argss) (cons arg acc)))))))) + +(struct $csp (vars + constraints + [assignments #:auto] + [checks #:auto]) #:mutable #:transparent + #:auto-value 0) +(define csp? $csp?) +(define vars $csp-vars) +(define constraints $csp-constraints) +(define-syntax-rule (in-constraints csp) (in-list ($csp-constraints csp))) +(define-syntax-rule (in-vars csp) (in-list ($csp-vars csp))) +(define-syntax-rule (in-var-names csp) (in-list (map $var-name ($csp-vars csp)))) + +(struct $constraint (names proc) #:transparent + #:property prop:procedure + (λ (constraint csp) + (unless ($csp? csp) + (raise-argument-error '$constraint-proc "$csp" csp)) + ;; apply proc in many-to-many style + (for/and ([args (in-cartesian (map (λ (cname) ($csp-vals csp cname)) ($constraint-names constraint)))]) + (apply ($constraint-proc constraint) args)))) + +(define (make-constraint [names null] [proc values]) + ($constraint names proc)) + +(define constraint-names $constraint-names) +(define constraint? $constraint?) + +(define (csp->graphviz csp) + (define g (csp->graph csp)) + (graphviz g #:colors (coloring/brelaz g))) + +(define (csp->graph csp) + (for*/fold ([g (unweighted-graph/undirected (map var-name (vars csp)))]) + ([constraint (in-constraints csp)] + [edge (in-combinations (constraint-names constraint) 2)]) + (apply add-edge! g edge) + g)) + +(struct $var (name domain) #:transparent) +(define var? $var?) +(define name? symbol?) +(define $var-vals $var-domain) +(define var-name $var-name) + +(struct $cvar $var (past) #:transparent) +(struct $avar $var () #:transparent) +(define assigned-var? $avar?) + +(define/contract (make-csp [vars null] [constraints null]) + (() ((listof var?) (listof constraint?)) . ->* . csp?) + ($csp vars constraints)) + +(define/contract (add-vars! csp names-or-procedure [vals-or-procedure empty]) + ((csp? (or/c (listof name?) procedure?)) ((or/c (listof any/c) procedure?)) . ->* . void?) + (for/fold ([vars ($csp-vars csp)] + #:result (set-$csp-vars! csp vars)) + ([name (in-list (if (procedure? names-or-procedure) + (names-or-procedure) + names-or-procedure))]) + (when (memq name (map var-name vars)) + (raise-argument-error 'add-vars! "var that doesn't already exist" name)) + (append vars (list ($var name + (if (procedure? vals-or-procedure) + (vals-or-procedure) + vals-or-procedure)))))) + +(define/contract (add-var! csp name [vals-or-procedure empty]) + ((csp? name?) ((or/c (listof any/c) procedure?)) . ->* . void?) + (add-vars! csp (list name) vals-or-procedure)) + +(define/contract (add-constraints! csp proc namess [proc-name #false]) + ((csp? procedure? (listof (listof name?))) ((or/c #false name?)) . ->* . void?) + (set-$csp-constraints! csp (append (constraints csp) + (for/list ([names (in-list namess)]) + (for ([name (in-list names)]) + (check-name-in-csp! 'add-constraints! csp name)) + (make-constraint names (if proc-name + (procedure-rename proc proc-name) + proc)))))) + +(define/contract (add-pairwise-constraint! csp proc var-names [proc-name #false]) + ((csp? procedure? (listof name?)) (name?) . ->* . void?) + (add-constraints! csp proc (combinations var-names 2) proc-name)) + +(define/contract (add-constraint! csp proc var-names [proc-name #false]) + ((csp? procedure? (listof name?)) (name?) . ->* . void?) + (add-constraints! csp proc (list var-names) proc-name)) + +(define/contract (alldiff= x y) + (any/c any/c . -> . boolean?) + (not (= x y))) + +(struct $backtrack (names) #: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-random (make-parameter #t)) +(define current-decompose (make-parameter #t)) + +(define/contract (check-name-in-csp! caller csp name) + (symbol? csp? name? . -> . void?) + (define names (map var-name (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? name? . -> . $var?) + (check-name-in-csp! 'csp-var csp name) + (for/first ([var (in-vars csp)] + #:when (eq? name (var-name var))) + var)) + +(define/contract ($csp-vals csp name) + (csp? 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 (assigned-name? csp name) + (csp? name? . -> . any/c) + (for/or ([var (in-vars csp)] + #:when (assigned-var? var)) + (eq? name (var-name var)))) + +(define (reduce-function-arity proc pattern) + (unless (match (procedure-arity proc) + [(arity-at-least val) (<= val (length pattern))] + [(? number? val) (= val (length pattern))]) + (raise-argument-error 'reduce-arity (format "list of length ~a, same as procedure arity" (procedure-arity proc)) pattern)) + (define reduced-arity-name (string->symbol (format "reduced-arity-~a" (object-name proc)))) + (define-values (boxed-id-names vals) (partition box? pattern)) + (define new-arity (length boxed-id-names)) + (procedure-rename + (λ xs + (unless (= (length xs) new-arity) + (apply raise-arity-error reduced-arity-name new-arity xs)) + (apply proc (for/fold ([acc empty] + [xs xs] + [vals vals] + #:result (reverse acc)) + ([pat-item (in-list pattern)]) + (if (box? pat-item) + (values (cons (car xs) acc) (cdr xs) vals) + (values (cons (car vals) acc) xs (cdr vals)))))) + reduced-arity-name)) + +(define/contract (reduce-constraint-arity csp [minimum-arity 3]) + ((csp?) ((or/c #false exact-nonnegative-integer?)) . ->* . csp?) + (let ([assigned-name? (curry assigned-name? csp)]) + (define (partially-assigned? constraint) + (ormap assigned-name? ($constraint-names constraint))) + (make-csp (vars csp) + (for/list ([constraint (in-constraints csp)]) + (cond + [(and (or (not minimum-arity) (<= minimum-arity (constraint-arity constraint))) + (partially-assigned? constraint)) + (match-define ($constraint cnames proc) constraint) + ($constraint (filter-not assigned-name? cnames) + ;; pattern is mix of values and boxed symbols (indicating variables to persist) + ;; use boxes here as cheap way to distinguish id symbols from value symbols + (let ([reduce-arity-pattern (for/list ([cname (in-list cnames)]) + (if (assigned-name? cname) + (first ($csp-vals csp cname)) + (box cname)))]) + (reduce-function-arity proc reduce-arity-pattern)))] + [else constraint]))))) + +(define nassns 0) +(define (reset-assns!) (set! nassns 0)) +(define/contract (assign-val csp name val) + (csp? name? any/c . -> . csp?) + (when-debug (set! nassns (add1 nassns))) + (make-csp + (for/list ([var (vars csp)]) + (if (eq? name (var-name var)) + ($avar name (list val)) + var)) + (constraints csp))) + +(define/contract (unassigned-vars csp) + (csp? . -> . (listof (and/c $var? (not/c assigned-var?)))) + (filter-not assigned-var? (vars csp))) + +(define/contract (first-unassigned-variable csp) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) + (match (unassigned-vars csp) + [(? empty?) #false] + [(cons x _) x])) + +(define/contract (minimum-remaining-values csp) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) + (match (unassigned-vars csp) + [(? empty?) #false] + [xs (argmin (λ (var) (length ($var-domain var))) xs)])) + +(define mrv minimum-remaining-values) + +(define/contract (var-degree csp var) + (csp? $var? . -> . exact-nonnegative-integer?) + (for/sum ([constraint (in-constraints csp)] + #:when (memq (var-name var) ($constraint-names constraint))) + 1)) + +(define/contract (blended-variable-selector csp) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) + (define uvars (unassigned-vars csp)) + (cond + [(empty? uvars) #false] + [(findf singleton-var? uvars)] + [else (first (let* ([uvars-by-mrv (sort uvars < #:key (λ (var) (length ($var-domain var))))] + [uvars-by-degree (sort uvars-by-mrv > #:key (λ (var) (var-degree csp var)))]) + uvars-by-degree))])) + +(define/contract (remaining-values var) + ($var? . -> . exact-nonnegative-integer?) + (length ($var-vals var))) + +(define/contract (mrv-degree-hybrid csp) + (csp? . -> . (or/c #f $var?)) + (define uvars (unassigned-vars csp)) + (cond + [(empty? uvars) #false] + [else + ;; minimum remaining values (MRV) rule + (define mrv-arg (argmin remaining-values uvars)) + (match (filter (λ (var) (= (remaining-values mrv-arg) (remaining-values var))) uvars) + [(list winning-uvar) winning-uvar] + [(list mrv-uvars ...) + ;; use degree as tiebreaker for mrv + (define degrees (map (λ (var) (var-degree csp var)) mrv-uvars)) + (define max-degree (apply max degrees)) + ;; use random tiebreaker for degree + (random-pick (for/list ([var (in-list mrv-uvars)] + [degree (in-list degrees)] + #:when (= max-degree degree)) + var))])])) + +(define first-domain-value values) + +(define (no-inference csp name) csp) + +(define/contract (relating-only constraints names) + ((listof $constraint?) (listof name?) . -> . (listof $constraint?)) + (for*/list ([constraint (in-list constraints)] + [cnames (in-value ($constraint-names constraint))] + #:when (and (= (length names) (length cnames)) + (for/and ([name (in-list names)]) + (memq name cnames)))) + constraint)) + +(define (binary-constraint? constraint) + (= 2 (constraint-arity constraint))) + +(define (constraint-relates? constraint name) + (memq name ($constraint-names constraint))) + +(define nfchecks 0) +(define (reset-nfcs!) (set! nfchecks 0)) + +(define/contract (forward-check csp aname) + (csp? name? . -> . csp?) + (define aval (first ($csp-vals csp aname))) + (define (check-var var) + (match var + ;; don't check against assigned vars, or the reference var + ;; (which is probably assigned but maybe not) + [(? (λ (x) (or (assigned-var? x) (eq? (var-name x) aname)))) var] + [($var name vals) + (match ((constraints csp) . relating-only . (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)) + ($cvar name new-vals (cons aname (if ($cvar? var) + ($cvar-past var) + null)))])])) + (define checked-vars (map check-var (vars csp))) + (when-debug (set! nfchecks (+ (length checked-vars) nchecks))) + ;; 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 ($cvar-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) + (backtrack! conflict-set)) + ;; Discard constraints that have produced singleton domains + ;; (they have no further use) + (define nonsingleton-constraints + (for/list ([constraint (in-constraints csp)] + #:unless (and + (binary-constraint? constraint) + (constraint-relates? constraint aname) + (let ([other-name (first (remq aname ($constraint-names constraint)))]) + (singleton-var? (csp-var csp other-name))))) + constraint)) + (make-csp checked-vars nonsingleton-constraints)) + +(define/contract (constraint-checkable? c names) + ($constraint? (listof name?) . -> . any/c) + ;; constraint is checkable if all constraint names + ;; are in target list of names. + (for/and ([cname (in-list ($constraint-names c))]) + (memq cname names))) + +(define/contract (constraint-arity constraint) + ($constraint? . -> . exact-nonnegative-integer?) + (length ($constraint-names constraint))) + +(define (singleton-var? var) + (= 1 (length ($var-domain var)))) + +(define nchecks 0) +(define (reset-nchecks!) (set! nchecks 0)) +(define/contract (check-constraints csp [mandatory-names #f] #:conflicts [conflict-count? #f]) + ((csp?) ((listof name?) #:conflicts boolean?) . ->* . (or/c csp? exact-nonnegative-integer?)) + ;; this time, we're not limited to assigned variables + ;; (that is, vars that have been deliberately assigned in the backtrack process thus far) + ;; we also want to use "singleton" vars (that is, vars that have been reduced to a single domain value by forward checking) + (define singleton-varnames (for/list ([var (in-vars csp)] + #:when (singleton-var? var)) + (var-name var))) + (define-values (checkable-constraints other-constraints) + (partition (λ (c) (and (constraint-checkable? c singleton-varnames) + (or (not mandatory-names) + (for/and ([name (in-list mandatory-names)]) + (constraint-relates? c name))))) + (constraints csp))) + (cond + [conflict-count? (define conflict-count + (for/sum ([constraint (in-list checkable-constraints)] + #:unless (constraint csp)) + 1)) + (when-debug (set! nchecks (+ conflict-count nchecks))) + conflict-count] + [else (for ([(constraint idx) (in-indexed (sort checkable-constraints < #:key constraint-arity))] + #:unless (constraint csp)) + (when-debug (set! nchecks (+ (add1 idx) nchecks))) + (backtrack!)) + ;; discard checked constraints, since they have no further reason to live + (make-csp (vars csp) other-constraints)])) + +(define/contract (make-nodes-consistent csp) + (csp? . -> . csp?) + ;; todo: why does this function slow down searches? + (make-csp + (for/list ([var (in-vars csp)]) + (match-define ($var name vals) var) + (define procs (for*/list ([constraint (in-constraints csp)] + [cnames (in-value ($constraint-names constraint))] + #:when (and (= 1 (length cnames)) (eq? name (car cnames)))) + ($constraint-proc constraint))) + ($var name + (for*/fold ([vals vals]) + ([proc (in-list procs)]) + (filter proc vals)))) + (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) (or (let ([btns ($backtrack-names exn)]) + (or (empty? btns) (memq name btns)))))) + (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))))]) + (let* ([csp (assign-val csp name val)] + ;; reduce constraints before inference, + ;; to create more forward-checkable (binary) constraints + [csp (reduce-constraint-arity csp)] + [csp (inference csp name)] + [csp (check-constraints csp)]) + (loop csp))) + conflicts)])))) + +(define (random-pick xs) + (list-ref xs (random (length xs)))) + +(define (assign-random-vals csp) + (for/fold ([new-csp csp]) + ([name (in-var-names csp)]) + (assign-val new-csp name (random-pick ($csp-vals csp name))))) + +(define (make-min-conflcts-thread csp0 thread-count max-steps [main-thread (current-thread)]) + (thread + (λ () + (let loop () + ;; Generate a complete assignment for all variables (probably with conflicts) + (for/fold ([csp (assign-random-vals csp0)]) + ([nth-step (in-range max-steps)]) + ;; Now repeatedly choose a random conflicted variable and change it + (match (conflicted-var-names csp) + [(? empty?) (thread-send main-thread csp) (loop)] + [names + (define name (random-pick names)) + (define val (min-conflicts-value csp name ($csp-vals csp0 name))) + (assign-val csp name val)])))))) + +(define/contract (min-conflicts-solver csp [max-steps 100]) + (($csp?) (integer?) . ->* . generator?) + ;; Solve a CSP by stochastic hillclimbing on the number of conflicts. + (generator () + (for ([thread-count 4]) ; todo: what is ideal thread count? + (make-min-conflcts-thread csp thread-count max-steps)) + (for ([i (in-naturals)]) + (yield (thread-receive))))) + +(define/contract (conflicted-var-names csp) + ($csp? . -> . (listof name?)) + ;; Return a list of variables in current assignment that are conflicted + (for/list ([name (in-var-names csp)] + #:when (positive? (nconflicts csp name))) + name)) + +(define/contract (min-conflicts-value csp name vals) + ($csp? name? (listof any/c) . -> . any/c) + ;; Return the value that will give var the least number of conflicts + (define vals-by-conflict (sort vals < #:key (λ (val) (nconflicts csp name val)) + #:cache-keys? #true)) + (for/first ([val (in-list vals-by-conflict)] + #:unless (equal? val (first ($csp-vals csp name)))) ;; but change the value + val)) + +(define no-value-sig (gensym)) + +(define/contract (nconflicts csp name [val no-value-sig]) + (($csp? name?) (any/c) . ->* . exact-nonnegative-integer?) + ;; How many conflicts var: val assignment has with other variables. + (check-constraints (if (eq? val no-value-sig) + csp + (assign-val csp name val)) (list name) #:conflicts #true)) + +(define/contract (csp->assocs csp) + (csp? . -> . (listof (cons/c name? any/c))) + (for/list ([var (in-vars csp)]) + (match var + [($var name (list val)) (cons name val)]))) + +(define/contract (combine-csps csps) + ((listof $csp?) . -> . $csp?) + (make-csp + (apply append (map $csp-vars csps)) + (apply append (map $csp-constraints csps)))) + +(define/contract (make-cartesian-generator solgens) + ((listof generator?) . -> . generator?) + (generator () + (define solstreams (for/list ([solgen (in-list solgens)]) + (for/stream ([sol (in-producer solgen (void))]) + sol))) + (let loop ([solstreams solstreams][sols empty]) + (if (null? solstreams) + (yield (combine-csps (reverse sols))) + (for ([sol (in-stream (car solstreams))]) + (loop (cdr solstreams) (cons sol sols))))))) + +(define/contract (extract-subcsp csp names) + ($csp? (listof name?) . -> . $csp?) + (make-csp + (for/list ([var (in-vars csp)] + #:when (memq (var-name var) names)) + var) + (for/list ([constraint (in-constraints csp)] + #:when (for/and ([cname (in-list ($constraint-names constraint))]) + (memq cname names))) + constraint))) + +(define/contract (solve* csp + #:finish-proc [finish-proc csp->assocs] + #:solver [solver (or (current-solver) backtracking-solver)] + #:limit [max-solutions +inf.0]) + ((csp?) (#:finish-proc procedure? #:solver procedure? #:limit exact-nonnegative-integer?) + . ->* . (listof any/c)) + (when-debug (reset-assns!) (reset-nfcs!) (reset-nchecks!)) + + (define subcsps ; decompose into independent csps. `cc` determines "connected components" + (if (current-decompose) + (for/list ([nodeset (in-list (cc (csp->graph csp)))]) + (extract-subcsp csp nodeset)) + (list csp))) + + (for/list ([solution (in-producer (make-cartesian-generator (map solver subcsps)) (void))] + [idx (in-range max-solutions)]) + (finish-proc solution))) + +(define/contract (solve csp + #:finish-proc [finish-proc csp->assocs] + #:solver [solver (or (current-solver) backtracking-solver)] + #:limit [max-solutions 1]) + ((csp?) (#:finish-proc procedure? #:solver procedure? #:limit exact-nonnegative-integer?) + . ->* . (or/c #false any/c)) + (match (solve* csp #:finish-proc finish-proc #:solver solver #:limit max-solutions) + [(list solution) solution] + [(list solutions ...) solutions] + [else #false])) + +(define (<> a b) (not (= a b))) +(define (neq? a b) (not (eq? a b))) + diff --git a/csp/main.rkt b/csp/csp/main.rkt similarity index 100% rename from csp/main.rkt rename to csp/csp/main.rkt diff --git a/csp/port/constraint.rkt b/csp/csp/port/constraint.rkt similarity index 100% rename from csp/port/constraint.rkt rename to csp/csp/port/constraint.rkt diff --git a/csp/port/domain.rkt b/csp/csp/port/domain.rkt similarity index 100% rename from csp/port/domain.rkt rename to csp/csp/port/domain.rkt diff --git a/csp/port/helper.rkt b/csp/csp/port/helper.rkt similarity index 100% rename from csp/port/helper.rkt rename to csp/csp/port/helper.rkt diff --git a/csp/port/main.rkt b/csp/csp/port/main.rkt similarity index 100% rename from csp/port/main.rkt rename to csp/csp/port/main.rkt diff --git a/csp/port/problem.rkt b/csp/csp/port/problem.rkt similarity index 100% rename from csp/port/problem.rkt rename to csp/csp/port/problem.rkt diff --git a/csp/port/solver.rkt b/csp/csp/port/solver.rkt similarity index 100% rename from csp/port/solver.rkt rename to csp/csp/port/solver.rkt diff --git a/csp/port/test-classes.rkt b/csp/csp/port/test-classes.rkt similarity index 100% rename from csp/port/test-classes.rkt rename to csp/csp/port/test-classes.rkt diff --git a/csp/port/test-einstein.rkt b/csp/csp/port/test-einstein.rkt similarity index 100% rename from csp/port/test-einstein.rkt rename to csp/csp/port/test-einstein.rkt diff --git a/csp/port/test-problems.rkt b/csp/csp/port/test-problems.rkt similarity index 100% rename from csp/port/test-problems.rkt rename to csp/csp/port/test-problems.rkt diff --git a/csp/port/variable.rkt b/csp/csp/port/variable.rkt similarity index 100% rename from csp/port/variable.rkt rename to csp/csp/port/variable.rkt diff --git a/csp/python-constraint/API Documentation.webloc b/csp/csp/python-constraint/API Documentation.webloc similarity index 100% rename from csp/python-constraint/API Documentation.webloc rename to csp/csp/python-constraint/API Documentation.webloc diff --git a/csp/python-constraint/LICENSE b/csp/csp/python-constraint/LICENSE similarity index 100% rename from csp/python-constraint/LICENSE rename to csp/csp/python-constraint/LICENSE diff --git a/csp/python-constraint/MANIFEST.in b/csp/csp/python-constraint/MANIFEST.in similarity index 100% rename from csp/python-constraint/MANIFEST.in rename to csp/csp/python-constraint/MANIFEST.in diff --git a/csp/python-constraint/PKG-INFO b/csp/csp/python-constraint/PKG-INFO similarity index 100% rename from csp/python-constraint/PKG-INFO rename to csp/csp/python-constraint/PKG-INFO diff --git a/csp/python-constraint/README b/csp/csp/python-constraint/README similarity index 100% rename from csp/python-constraint/README rename to csp/csp/python-constraint/README diff --git a/csp/python-constraint/constraint.py b/csp/csp/python-constraint/constraint.py similarity index 100% rename from csp/python-constraint/constraint.py rename to csp/csp/python-constraint/constraint.py diff --git a/csp/python-constraint/examples/abc/abc.py b/csp/csp/python-constraint/examples/abc/abc.py similarity index 100% rename from csp/python-constraint/examples/abc/abc.py rename to csp/csp/python-constraint/examples/abc/abc.py diff --git a/csp/python-constraint/examples/coins/coins.py b/csp/csp/python-constraint/examples/coins/coins.py similarity index 100% rename from csp/python-constraint/examples/coins/coins.py rename to csp/csp/python-constraint/examples/coins/coins.py diff --git a/csp/python-constraint/examples/crosswords/crosswords.py b/csp/csp/python-constraint/examples/crosswords/crosswords.py similarity index 100% rename from csp/python-constraint/examples/crosswords/crosswords.py rename to csp/csp/python-constraint/examples/crosswords/crosswords.py diff --git a/csp/python-constraint/examples/crosswords/large.mask b/csp/csp/python-constraint/examples/crosswords/large.mask similarity index 100% rename from csp/python-constraint/examples/crosswords/large.mask rename to csp/csp/python-constraint/examples/crosswords/large.mask diff --git a/csp/python-constraint/examples/crosswords/medium.mask b/csp/csp/python-constraint/examples/crosswords/medium.mask similarity index 100% rename from csp/python-constraint/examples/crosswords/medium.mask rename to csp/csp/python-constraint/examples/crosswords/medium.mask diff --git a/csp/python-constraint/examples/crosswords/python.mask b/csp/csp/python-constraint/examples/crosswords/python.mask similarity index 100% rename from csp/python-constraint/examples/crosswords/python.mask rename to csp/csp/python-constraint/examples/crosswords/python.mask diff --git a/csp/python-constraint/examples/crosswords/small.mask b/csp/csp/python-constraint/examples/crosswords/small.mask similarity index 100% rename from csp/python-constraint/examples/crosswords/small.mask rename to csp/csp/python-constraint/examples/crosswords/small.mask diff --git a/csp/python-constraint/examples/einstein/einstein.py b/csp/csp/python-constraint/examples/einstein/einstein.py similarity index 100% rename from csp/python-constraint/examples/einstein/einstein.py rename to csp/csp/python-constraint/examples/einstein/einstein.py diff --git a/csp/python-constraint/examples/einstein/einstein2.py b/csp/csp/python-constraint/examples/einstein/einstein2.py similarity index 100% rename from csp/python-constraint/examples/einstein/einstein2.py rename to csp/csp/python-constraint/examples/einstein/einstein2.py diff --git a/csp/python-constraint/examples/queens/queens.py b/csp/csp/python-constraint/examples/queens/queens.py similarity index 100% rename from csp/python-constraint/examples/queens/queens.py rename to csp/csp/python-constraint/examples/queens/queens.py diff --git a/csp/python-constraint/examples/rooks/rooks.py b/csp/csp/python-constraint/examples/rooks/rooks.py similarity index 100% rename from csp/python-constraint/examples/rooks/rooks.py rename to csp/csp/python-constraint/examples/rooks/rooks.py diff --git a/csp/python-constraint/examples/studentdesks/studentdesks.py b/csp/csp/python-constraint/examples/studentdesks/studentdesks.py similarity index 100% rename from csp/python-constraint/examples/studentdesks/studentdesks.py rename to csp/csp/python-constraint/examples/studentdesks/studentdesks.py diff --git a/csp/python-constraint/examples/sudoku/sudoku.py b/csp/csp/python-constraint/examples/sudoku/sudoku.py similarity index 100% rename from csp/python-constraint/examples/sudoku/sudoku.py rename to csp/csp/python-constraint/examples/sudoku/sudoku.py diff --git a/csp/python-constraint/examples/wordmath/seisseisdoze.py b/csp/csp/python-constraint/examples/wordmath/seisseisdoze.py similarity index 100% rename from csp/python-constraint/examples/wordmath/seisseisdoze.py rename to csp/csp/python-constraint/examples/wordmath/seisseisdoze.py diff --git a/csp/python-constraint/examples/wordmath/sendmoremoney.py b/csp/csp/python-constraint/examples/wordmath/sendmoremoney.py similarity index 100% rename from csp/python-constraint/examples/wordmath/sendmoremoney.py rename to csp/csp/python-constraint/examples/wordmath/sendmoremoney.py diff --git a/csp/python-constraint/examples/wordmath/twotwofour.py b/csp/csp/python-constraint/examples/wordmath/twotwofour.py similarity index 100% rename from csp/python-constraint/examples/wordmath/twotwofour.py rename to csp/csp/python-constraint/examples/wordmath/twotwofour.py diff --git a/csp/python-constraint/examples/xsum/xsum.py b/csp/csp/python-constraint/examples/xsum/xsum.py similarity index 100% rename from csp/python-constraint/examples/xsum/xsum.py rename to csp/csp/python-constraint/examples/xsum/xsum.py diff --git a/csp/python-constraint/setup.cfg b/csp/csp/python-constraint/setup.cfg similarity index 100% rename from csp/python-constraint/setup.cfg rename to csp/csp/python-constraint/setup.cfg diff --git a/csp/python-constraint/setup.py b/csp/csp/python-constraint/setup.py similarity index 100% rename from csp/python-constraint/setup.py rename to csp/csp/python-constraint/setup.py diff --git a/csp/python-constraint/testconstraint.py b/csp/csp/python-constraint/testconstraint.py similarity index 100% rename from csp/python-constraint/testconstraint.py rename to csp/csp/python-constraint/testconstraint.py diff --git a/csp/python-constraint/trials/abcd.py b/csp/csp/python-constraint/trials/abcd.py similarity index 100% rename from csp/python-constraint/trials/abcd.py rename to csp/csp/python-constraint/trials/abcd.py diff --git a/csp/python-constraint/trials/coins.py b/csp/csp/python-constraint/trials/coins.py similarity index 100% rename from csp/python-constraint/trials/coins.py rename to csp/csp/python-constraint/trials/coins.py diff --git a/csp/python-constraint/trials/constraint.py b/csp/csp/python-constraint/trials/constraint.py similarity index 100% rename from csp/python-constraint/trials/constraint.py rename to csp/csp/python-constraint/trials/constraint.py diff --git a/csp/python-constraint/trials/crosswords.py b/csp/csp/python-constraint/trials/crosswords.py similarity index 100% rename from csp/python-constraint/trials/crosswords.py rename to csp/csp/python-constraint/trials/crosswords.py diff --git a/csp/python-constraint/trials/einstein.py b/csp/csp/python-constraint/trials/einstein.py similarity index 100% rename from csp/python-constraint/trials/einstein.py rename to csp/csp/python-constraint/trials/einstein.py diff --git a/csp/python-constraint/trials/einstein2.py b/csp/csp/python-constraint/trials/einstein2.py similarity index 100% rename from csp/python-constraint/trials/einstein2.py rename to csp/csp/python-constraint/trials/einstein2.py diff --git a/csp/python-constraint/trials/large.mask b/csp/csp/python-constraint/trials/large.mask similarity index 100% rename from csp/python-constraint/trials/large.mask rename to csp/csp/python-constraint/trials/large.mask diff --git a/csp/python-constraint/trials/medium.mask b/csp/csp/python-constraint/trials/medium.mask similarity index 100% rename from csp/python-constraint/trials/medium.mask rename to csp/csp/python-constraint/trials/medium.mask diff --git a/csp/python-constraint/trials/python.mask b/csp/csp/python-constraint/trials/python.mask similarity index 100% rename from csp/python-constraint/trials/python.mask rename to csp/csp/python-constraint/trials/python.mask diff --git a/csp/python-constraint/trials/queens.py b/csp/csp/python-constraint/trials/queens.py similarity index 100% rename from csp/python-constraint/trials/queens.py rename to csp/csp/python-constraint/trials/queens.py diff --git a/csp/python-constraint/trials/rooks.py b/csp/csp/python-constraint/trials/rooks.py similarity index 100% rename from csp/python-constraint/trials/rooks.py rename to csp/csp/python-constraint/trials/rooks.py diff --git a/csp/python-constraint/trials/seisseisdoze.py b/csp/csp/python-constraint/trials/seisseisdoze.py similarity index 100% rename from csp/python-constraint/trials/seisseisdoze.py rename to csp/csp/python-constraint/trials/seisseisdoze.py diff --git a/csp/python-constraint/trials/sendmoremoney.py b/csp/csp/python-constraint/trials/sendmoremoney.py similarity index 100% rename from csp/python-constraint/trials/sendmoremoney.py rename to csp/csp/python-constraint/trials/sendmoremoney.py diff --git a/csp/python-constraint/trials/small.mask b/csp/csp/python-constraint/trials/small.mask similarity index 100% rename from csp/python-constraint/trials/small.mask rename to csp/csp/python-constraint/trials/small.mask diff --git a/csp/python-constraint/trials/studentdesks.py b/csp/csp/python-constraint/trials/studentdesks.py similarity index 100% rename from csp/python-constraint/trials/studentdesks.py rename to csp/csp/python-constraint/trials/studentdesks.py diff --git a/csp/python-constraint/trials/sudoku.py b/csp/csp/python-constraint/trials/sudoku.py similarity index 100% rename from csp/python-constraint/trials/sudoku.py rename to csp/csp/python-constraint/trials/sudoku.py diff --git a/csp/python-constraint/trials/twotwofour.py b/csp/csp/python-constraint/trials/twotwofour.py similarity index 100% rename from csp/python-constraint/trials/twotwofour.py rename to csp/csp/python-constraint/trials/twotwofour.py diff --git a/csp/python-constraint/trials/xsum.py b/csp/csp/python-constraint/trials/xsum.py similarity index 100% rename from csp/python-constraint/trials/xsum.py rename to csp/csp/python-constraint/trials/xsum.py diff --git a/csp/csp/scribblings/csp.scrbl b/csp/csp/scribblings/csp.scrbl new file mode 100644 index 00000000..158d5897 --- /dev/null +++ b/csp/csp/scribblings/csp.scrbl @@ -0,0 +1,31 @@ +#lang scribble/manual + +@(require scribble/eval (for-label racket csp)) + +@(define my-eval (make-base-eval)) +@(my-eval `(require csp)) + + +@title{csp} + +@author[(author+email "Matthew Butterick" "mb@mbtype.com")] + + +@defmodule[csp] + +A simple hyphenation engine that uses the Knuth–Liang hyphenation algorithm originally developed for TeX. I have added little to their work. Accordingly, I take little credit. + +@section{Installation} + +At the command line: +@verbatim{raco pkg install csp} + +After that, you can update the package like so: +@verbatim{raco pkg update csp} + +@section{License & source code} + +This module is licensed under the LGPL. + +Source repository at @link["http://github.com/mbutterick/csp"]{http://github.com/mbutterick/csp}. Suggestions & corrections welcome. + diff --git a/csp/info.rkt b/csp/info.rkt index bb3b0a3f..5b7681d8 100644 --- a/csp/info.rkt +++ b/csp/info.rkt @@ -1,7 +1,6 @@ #lang info -(define collection "csp") -(define deps '(("base" #:version "6.0") "sugar" "rackunit-lib")) +(define collection 'multi) +(define deps '(("base" #:version "6.0") "sugar" "rackunit-lib" "debug" "graph")) (define update-implies '("sugar")) -;(define scribblings '(("scribblings/pollen.scrbl" (multi-page)))) -;(define raco-commands '(("pollen" pollen/raco "issue Pollen command" #f))) +(define scribblings '(("csp/scribblings/csp.scrbl" (multi-page)))) ;(define compile-omit-paths '("tests" "raco.rkt")) \ No newline at end of file