#| -*-Scheme-*- $Id$ Copyright 2006 Massachusetts Institute of Technology This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. |# ;;;; Simple constraint propagator (declare (usual-integrations)) (define-record-type (cp:%make-network name value-model tms connectors activation-queue) cp:network? (name cp:network-name) (value-model cp:network-value-model) (tms cp:network-tms) (connectors cp:network-connectors cp:set-network-connectors!) (activation-queue cp:activation-queue)) (define-guarantee cp:network "constraint network") (define (cp:make-network name value-model) (cp:%make-network name value-model (tms:make name cp:event-handler) '() (cp:make-queue))) (define (cp:add-connector-to-network connector network) (cp:set-network-connectors! network (cons connector (cp:network-connectors network)))) (define (cp:network-value-models network) (let loop ((connectors (cp:network-connectors network)) (models (list (cp:network-value-model network)))) (if (pair? connectors) (loop (cdr connectors) (let ((model (cp:%connector-value-model (car connectors)))) (if (and model (not (memq model models))) (cons model models) models))) models))) (define (cp:post-propagation-hooks network) (let loop ((models (cp:network-value-models network)) (hooks '())) (if (pair? models) (loop (cdr models) (let ((hook (cp:value-post-propagation-hook (car models)))) (if hook (cons hook hooks) hooks))) hooks))) (define-record-type (cp:%make-value-model acceptance-predicate equality-predicate value-chooser coincidence-handler post-propagation-hook) cp:value-model? (acceptance-predicate cp:value-acceptance-predicate) (equality-predicate cp:value-equality-predicate) (value-chooser cp:value-chooser) (coincidence-handler cp:value-coincidence-handler) (post-propagation-hook cp:value-post-propagation-hook)) (define-guarantee cp:value-model "constraint value model") (define (cp:make-value-model acceptance-predicate equality-predicate #!optional value-chooser coincidence-handler post-propagation-hook) (cp:%make-value-model acceptance-predicate equality-predicate (if (or (default-object? value-chooser) (not value-chooser)) (lambda (values) (car values)) value-chooser) (if (or (default-object? coincidence-handler) (not coincidence-handler)) cp:default-coincidence-handler coincidence-handler) (if (default-object? post-propagation-hook) #f post-propagation-hook))) (define (cp:default-coincidence-handler connector value justify) (let ((assignment (cp:supported-assignment connector))) (if (not (cp:values-equal? (cp:assignment-value assignment) value connector)) (cp:set-contradiction connector value justify)))) (define (cp:value-acceptable? v connector) ((cp:value-acceptance-predicate (cp:connector-value-model connector)) v)) (define (cp:values-equal? v1 v2 connector) ((cp:value-equality-predicate (cp:connector-value-model connector)) v1 v2)) (define (cp:choose-value values connector) ((cp:value-chooser (cp:connector-value-model connector)) values)) (define (cp:handle-coincidence connector value justify) ((cp:value-coincidence-handler (cp:connector-value-model connector)) connector value justify)) ;;;; Constraints (define-record-type (cp:%make-constraint-type name propagator validator) cp:constraint-type? (name cp:constraint-type-name) (propagator cp:constraint-type-propagator) (validator cp:constraint-type-validator)) (define-guarantee cp:constraint-type "constraint type") (define (cp:make-constraint-type name propagator #!optional validator) (cp:%make-constraint-type name propagator (if (default-object? validator) #f validator))) (define-record-type (cp:%make-constraint type name network connectors) cp:constraint? (type cp:constraint-type) (name cp:constraint-name) (network cp:constraint-network) (connectors cp:constraint-connectors)) (define-guarantee cp:constraint "constraint") (define (cp:make-constraint type name network connector . connectors) (let ((connectors (cons connector connectors))) (for-each (lambda (connector) (guarantee-cp:connector connector 'cp:make-constraint)) connectors) (let ((validator (cp:constraint-type-validator type))) (if validator (validator connectors))) (let ((constraint (cp:%make-constraint type name network connectors))) (for-each (lambda (connector) (cp:add-constraint-to-connector constraint connector)) connectors) (cp:awaken-constraint constraint) constraint))) ;;;; Connectors (define-record-type (cp:%make-connector name network value-model constraints assignments) cp:connector? (name cp:connector-name) (network cp:connector-network) (value-model cp:%connector-value-model) (constraints cp:connector-constraints cp:set-connector-constraints!) (assignments cp:connector-assignments cp:set-connector-assignments!)) (define-guarantee cp:connector "connector") (set-record-type-unparser-method! (standard-unparser-method 'connector (lambda (connector port) (write-char #\space port) (write (cp:connector-name connector) port)))) (define (cp:make-connector name network #!optional value-model) (let ((value-model (if (default-object? value-model) #f value-model))) (if value-model (guarantee-cp:value-model value-model 'cp:make-connector)) (let ((connector (cp:%make-connector name network value-model '() '()))) (cp:add-connector-to-network connector network) connector))) (define (cp:connector-value-model connector) (or (cp:%connector-value-model connector) (cp:network-value-model (cp:connector-network connector)))) (define (cp:add-constraint-to-connector constraint connector) (cp:set-connector-constraints! connector (cons constraint (cp:connector-constraints connector)))) (define (cp:add-assignment-to-connector assignment connector) (cp:set-connector-assignments! connector (append! (cp:connector-assignments connector) (list assignment)))) (define (cp:supported-assignments connector) (keep-matching-items (cp:connector-assignments connector) tms:node-supported?)) (define (cp:supported-assignment connector) (let ((assignments (cp:supported-assignments connector))) (if (not (pair? assignments)) (error:bad-range-argument connector 'cp:supported-assignment)) (let ((value (cp:choose-value (map cp:assignment-value assignments) connector))) (let ((assignment (find-matching-item assignments (lambda (assignment) (cp:assignment-has-value? assignment value))))) (if (not assignment) (error "Can't find matching assignment:" value)) assignment)))) (define (cp:has-value? connector) (there-exists? (cp:connector-assignments connector) tms:node-supported?)) (define (cp:value-of connector) (cp:assignment-value (cp:supported-assignment connector))) (define (cp:values-of connector) (map cp:assignment-value (cp:supported-assignments connector))) (define (cp:value-is? connector value) (and (cp:has-value? connector) (cp:values-equal? (cp:value-of connector) value connector))) (define (cp:find-assignment connector value) (find-matching-item (cp:connector-assignments connector) (lambda (assignment) (cp:assignment-has-value? assignment value)))) (define (cp:assignment-for-value connector value) (or (cp:find-assignment connector value) (cp:make-assignment connector value))) ;;;; Assignments (define (cp:make-assignment connector value) (if (not (cp:value-acceptable? value connector)) (error:bad-range-argument value 'cp:make-assignment)) (let ((assignment (tms:make-node (cp:network-tms (cp:connector-network connector)) (cp:make-assignment-datum connector value)))) (cp:add-assignment-to-connector assignment connector) assignment)) (define (cp:assignment? object) (and (tms:node? object) (cp:assignment-datum? (tms:node-datum object) ))) (define (cp:assignment-connector assignment) (cp:assignment-datum-connector (tms:node-datum assignment))) (define (cp:assignment-value assignment) (cp:assignment-datum-value (tms:node-datum assignment))) (define (cp:assignment-has-value? assignment value) (let ((datum (tms:node-datum assignment))) (cp:values-equal? (cp:assignment-datum-value datum) value (cp:assignment-datum-connector datum)))) (define-record-type (cp:make-assignment-datum connector value) cp:assignment-datum? (connector cp:assignment-datum-connector) (value cp:assignment-datum-value)) (define (cp:assume-value connector value) (if (cp:has-value? connector) (cp:handle-coincidence connector value tms:assume-node) (cp:set-justified-value connector value tms:assume-node)) (cp:propagate (cp:connector-network connector))) (define (cp:retract-value connector) (let loop ((assignments (cp:connector-assignments connector)) (retractions? #f)) (if (pair? assignments) (if (tms:node-assumed? (car assignments)) (begin (tms:retract-node (car assignments)) (loop (cdr assignments) #t)) (loop (cdr assignments) retractions?)) (begin (if retractions? (cp:propagate (cp:connector-network connector))) retractions?)))) ;;;; Assigning values (define (cp:set-value connector value operands constraint) (let* ((supports (map cp:supported-assignment operands)) (justify (lambda (node) (tms:justify-node node constraint supports)))) (if (cp:has-value? connector) (if (cp:constraint-satisfied? constraint (cons (cp:supported-assignment connector) supports)) unspecific (cp:handle-coincidence connector value justify)) (cp:set-justified-value connector value justify)))) (define (cp:constraint-satisfied? constraint supports) (there-exists? supports (lambda (support) (there-exists? (tms:node-justifications support) (lambda (j) (and (eq? (tms:justification-rule j) constraint) (tms:justification-supported? j) (for-all? (tms:justification-antecedents j) (lambda (node) (memq node supports))))))))) (define (cp:set-justified-value connector value justify) (let ((assignment (cp:assignment-for-value connector value))) (justify assignment) assignment)) (define (cp:set-contradiction connector value justify) (cp:signal-contradiction connector (let ((current (cp:supported-assignment connector))) ;; Force order of evaluation (list (cp:set-justified-value connector value justify) current)))) (define (cp:signal-contradiction connector assignments) (tms:justify-node (tms:contradiction (cp:network-tms (cp:connector-network connector))) connector assignments)) ;;;; Propagation (define (cp:event-handler node justification) (if (cp:assignment? node) (cp:awaken-connector (cp:assignment-connector node)))) (define (cp:awaken-connector connector) (cp:awaken-constraints (cp:connector-constraints connector))) (define (cp:awaken-constraints constraints) (for-each cp:awaken-constraint constraints)) (define (cp:awaken-constraint constraint) (cp:enqueue constraint (cp:activation-queue (cp:constraint-network constraint)))) (define (cp:propagate network) (let ((queue (cp:activation-queue network)) (contra (tms:contradiction (cp:network-tms network))) (hooks (cp:post-propagation-hooks network))) (let loop () (if (and (tms:node-supported? contra) cp:warn-on-contradiction?) (warn "Contradiction:" contra)) (cond ((cp:non-empty-queue? queue) (let ((constraint (cp:dequeue queue))) ;;(write-line `(activate ,constraint)) ((cp:constraint-type-propagator (cp:constraint-type constraint)) constraint) (loop))) ((pair? hooks) (for-each (lambda (hook) (hook network)) hooks) (if (cp:non-empty-queue? queue) (loop))))))) (define cp:warn-on-contradiction? #t) (define (cp:make-queue) (cons '() '())) (define (cp:non-empty-queue? queue) (pair? (car queue))) (define (cp:enqueue item queue) (if (not (memq item (car queue))) ;don't queue twice (let ((old (cdr queue)) (new (list item))) (if (pair? old) (set-cdr! old new) (set-car! queue new)) (set-cdr! queue new)))) (define (cp:dequeue queue) (let ((old (car queue))) (let ((new (cdr old))) (set-car! queue new) (if (not (pair? new)) (set-cdr! queue new))) (car old)))