From 667ccecda22d800432d79b50812818807b2ff340 Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Sat, 20 Oct 2018 10:06:33 -0700 Subject: [PATCH] slower --- csp/hacs.rkt | 197 ++++++++++++++++++++++++++++----------------------- 1 file changed, 107 insertions(+), 90 deletions(-) diff --git a/csp/hacs.rkt b/csp/hacs.rkt index 908fc7b5..de2fa88a 100644 --- a/csp/hacs.rkt +++ b/csp/hacs.rkt @@ -12,6 +12,12 @@ (struct $csp ([vars #:mutable] [constraints #:mutable]) #:transparent) +(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))) + (struct $constraint (names proc) #:transparent #:property prop:procedure (λ (constraint csp) @@ -21,24 +27,29 @@ (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)) + (struct $var (name domain) #:transparent) -(define $var-name? symbol?) +(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 (make-csp [vds null] [constraints null]) - ($csp vds constraints)) +(define (make-csp [vars null] [constraints null]) + ($csp vars constraints)) (define/contract (add-vars! csp names-or-procedure [vals-or-procedure empty]) - (($csp? (or/c (listof $var-name?) procedure?)) ((or/c (listof any/c) procedure?)) . ->* . void?) + ((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)) + (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) @@ -46,25 +57,25 @@ vals-or-procedure)))))) (define/contract (add-var! csp name [vals-or-procedure empty]) - (($csp? $var-name?) ((or/c (listof any/c) procedure?)) . ->* . void?) + ((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 $var-name?))) ((or/c #false $var-name?)) . ->* . void?) - (set-$csp-constraints! csp (append ($csp-constraints csp) + ((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)) - ($constraint names (if proc-name - (procedure-rename proc proc-name) - proc)))))) + (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 $var-name?)) ($var-name?) . ->* . void?) + ((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 $var-name?)) ($var-name?) . ->* . void?) + ((csp? procedure? (listof name?)) (name?) . ->* . void?) (add-constraints! csp proc (list var-names) proc-name)) (define/contract (alldiff= x y) @@ -74,6 +85,7 @@ (struct inconsistency-signal (csp) #:transparent) (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)) @@ -82,28 +94,30 @@ (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))) + (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? $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))) +(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? $var-name? . -> . (listof any/c)) - (check-name-in-csp! '$csp-vals csp name) - ($var-domain ($csp-var 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? $var-name? . -> . boolean?) - (and (memq name (map $var-name (filter $avar? ($csp-vars csp)))) #true)) + (csp? name? . -> . any/c) + (for/first ([var (in-vars csp)] + #:when (assigned-var? var)) + (eq? name (var-name var)))) (define (reduce-arity proc pattern) (unless (match (procedure-arity proc) @@ -112,11 +126,10 @@ (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 id-names (map unbox boxed-id-names)) - (define new-arity (length id-names)) + (define new-arity (length boxed-id-names)) (procedure-rename (λ xs - (unless (= (length xs) new-arity) + (unless (= (length xs) new-arity) (apply raise-arity-error reduced-arity-name new-arity xs)) (apply proc (for/fold ([acc empty] [xs xs] @@ -129,47 +142,44 @@ reduced-arity-name)) (define/contract (reduce-constraint-arity csp [minimum-arity 3]) - (($csp?) ((or/c #false exact-nonnegative-integer?)) . ->* . $csp?) + ((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))) - ($csp ($csp-vars csp) - (for/list ([constraint (in-list ($csp-constraints csp))]) - (cond - [(and (if minimum-arity (<= minimum-arity (constraint-arity constraint)) #true) - (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-arity proc reduce-arity-pattern)))] - [else constraint]))))) + (make-csp (vars csp) + (for/list ([constraint (in-constraints csp)]) + (cond + [(and (if minimum-arity (<= minimum-arity (constraint-arity constraint)) #true) + (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-arity proc reduce-arity-pattern)))] + [else constraint]))))) (define/contract (assign-val csp name val) - ($csp? $var-name? any/c . -> . $csp?) - (define assigned-csp ($csp - (for/list ([var ($csp-vars csp)]) - (if (eq? name ($var-name var)) - ($avar name (list val)) - var)) - ($csp-constraints csp))) - assigned-csp) + (csp? name? any/c . -> . csp?) + (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 $avar?)))) - (for/list ([var (in-list ($csp-vars csp))] - #:unless ($avar? var)) - var)) + (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 $avar?)))) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) (match (unassigned-vars csp) [(? empty?) #false] - [xs (first xs)])) + [(cons x _) x])) (define/contract (argmin-random-tie proc xs) (procedure? (non-empty-listof any/c) . -> . any/c) @@ -180,21 +190,22 @@ (first xs))) (define/contract (minimum-remaining-values csp) - ($csp? . -> . (or/c #false (and/c $var? (not/c $avar?)))) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) (match (unassigned-vars csp) [(? empty?) #false] [xs (argmin-random-tie (λ (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-list ($csp-constraints csp))] - #:when (memq ($var-name var) ($constraint-names constraint))) + (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 $avar?)))) + (csp? . -> . (or/c #false (and/c $var? (not/c assigned-var?)))) (define uvars (unassigned-vars csp)) (cond [(empty? uvars) #false] @@ -208,7 +219,7 @@ (length ($var-vals var))) (define/contract (mrv-degree-hybrid csp) - ($csp? . -> . (or/c #f $var?)) + (csp? . -> . (or/c #f $var?)) (define uvars (unassigned-vars csp)) (cond [(empty? uvars) #false] @@ -228,7 +239,7 @@ (define (no-inference csp name) csp) (define/contract (relating-only constraints names) - ((listof $constraint?) (listof $var-name?) . -> . (listof $constraint?)) + ((listof $constraint?) (listof name?) . -> . (listof $constraint?)) (for*/list ([constraint (in-list constraints)] [cnames (in-value ($constraint-names constraint))] #:when (and (= (length names) (length cnames)) @@ -236,16 +247,22 @@ (memq name cnames)))) constraint)) +(define (binary-constraint? constraint) + (= 2 (constraint-arity constraint))) + +(define (constraint-relates? constraint name) + (and (memq name ($constraint-names constraint)) #true)) + (define/contract (forward-check csp aname) - ($csp? $var-name? . -> . $csp?) + (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 ($avar? x) (eq? ($var-name x) aname)))) var] + [(? (λ (x) (or (assigned-var? x) (eq? (var-name x) aname)))) var] [($var name vals) - (match (($csp-constraints csp) . relating-only . (list aname name)) + (match ((constraints csp) . relating-only . (list aname name)) [(? empty?) var] [constraints (define new-vals @@ -259,7 +276,7 @@ ($cvar name new-vals (cons aname (if ($cvar? var) ($cvar-past var) null)))])])) - (define checked-vars (map check-var ($csp-vars csp))) + (define checked-vars (map check-var (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)) @@ -271,21 +288,21 @@ ;; 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))) + (backtrack! conflict-set)) ;; Discard constraints that have produced singleton domains ;; (they have no further use) (define nonsingleton-constraints - (for/list ([constraint (in-list ($csp-constraints csp))] + (for/list ([constraint (in-constraints csp)] #:unless (and - (= 2 (constraint-arity constraint)) ; binary constraint - (memq aname ($constraint-names constraint)) ; includes target name + (binary-constraint? constraint) + (constraint-relates? constraint aname) (let ([other-name (first (remq aname ($constraint-names constraint)))]) ; and something else (= (length ($csp-vals csp other-name)) 1)))) ; that has only one value constraint)) - ($csp checked-vars nonsingleton-constraints)) + (make-csp checked-vars nonsingleton-constraints)) (define/contract (constraint-checkable? c names) - ($constraint? (listof $var-name?) . -> . boolean?) + ($constraint? (listof name?) . -> . boolean?) (and (for/and ([cname (in-list ($constraint-names c))]) (memq cname names)) #true)) @@ -298,27 +315,27 @@ (= 1 (length ($var-domain var)))) (define/contract (check-constraints csp) - ($csp? . -> . $csp?) + (csp? . -> . csp?) ;; 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-list ($csp-vars csp))] + (define singleton-varnames (for/list ([var (in-vars csp)] #:when (singleton-var? var)) - ($var-name var))) + (var-name var))) (define-values (checkable-constraints other-constraints) - (partition (λ (c) (constraint-checkable? c singleton-varnames)) ($csp-constraints csp))) + (partition (λ (c) (constraint-checkable? c singleton-varnames)) (constraints csp))) (for ([constraint (in-list (sort checkable-constraints < #:key constraint-arity))] #:unless (constraint csp)) - (raise ($backtrack null))) - ($csp ($csp-vars csp) other-constraints)) + (backtrack!)) + (make-csp (vars csp) other-constraints)) (define/contract (make-nodes-consistent csp) - ($csp? . -> . $csp?) + (csp? . -> . csp?) ;; todo: why does this function slow down searches? ($csp - (for/list ([var (in-list ($csp-vars csp))]) + (for/list ([var (in-vars csp)]) (match-define ($var name vals) var) - (define procs (for*/list ([constraint (in-list ($csp-constraints csp))] + (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))) @@ -326,7 +343,7 @@ (for*/fold ([vals vals]) ([proc (in-list procs)]) (filter proc vals)))) - ($csp-constraints csp))) + (constraints csp))) (define/contract (backtracking-solver csp @@ -334,7 +351,7 @@ (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?) + ((csp?) (#:select-variable procedure? #:order-values procedure? #:inference procedure?) . ->* . generator?) (generator () (let loop ([csp csp]) (match (select-unassigned-variable csp) @@ -360,8 +377,8 @@ ;; todo: min-conflicts solver (define/contract ($csp-assocs csp) - ($csp? . -> . (listof (cons/c $var-name? any/c))) - (for/list ([var (in-list ($csp-vars csp))]) + (csp? . -> . (listof (cons/c name? any/c))) + (for/list ([var (in-vars csp)]) (match var [($var name domain) (cons name (first domain))]))) @@ -369,7 +386,7 @@ #:finish-proc [finish-proc $csp-assocs] #:solver [solver (or (current-solver) backtracking-solver)] #:count [max-solutions +inf.0]) - (($csp?) (#:finish-proc procedure? #:solver procedure? #:count integer?) . ->* . (listof any/c)) + ((csp?) (#:finish-proc procedure? #:solver procedure? #:count integer?) . ->* . (listof any/c)) (for/list ([solution (in-producer (solver csp) (void))] [idx (in-range max-solutions)]) (finish-proc solution))) @@ -377,7 +394,7 @@ (define/contract (solve csp #:finish-proc [finish-proc $csp-assocs] #:solver [solver (or (current-solver) backtracking-solver)]) - (($csp?) (#:finish-proc procedure? #:solver procedure?) . ->* . (or/c #false any/c)) + ((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]))