STRUCTURE AND INTERPRETATION OF MUSIC CONCEPTS ______________________________________________ ============================================== CLASS 7: PROPAGATION OF CONSTRAINTS (SICP 3.3.5) _______________________________________________ =============================================== 1| 2| 3| 4| Mathematical relationships like equations or inequalities impose 5| dependencies, with no specified direction, among objects. For example: 6| 2*A = 5*(B + 2 * A ) 7| imposes certain relationship between A and B. If A is 1, then B is 8| -8/5, if B is 1 then A is -5/8. 9| We are interested in a language that enables us to work in terms of 10| relations, i.e., no specified direction for a computation. 11| 12| The language consists of PRIMITIVE CONSTRAINTS and CONNECTORS. 13| *** The primitive elements of the language are PRIMITIVE CONSTRAINTS, 14| that state certain relations that must hold between quantities. 15| For example: 16| (adder a b c) specifies the relation a + b = c 17| (multiplier a b c) specifies the relation a * b = c 18| (constant 3.14 pi) specifies that the value of pi must be 3.14. 19| In general, these primitive constraints have the arguments: 20| Adder: a1, a2, s. 21| Multiplier: m1, m2, p. 22| Constant: num, c. 23| *** CONNECTORS provide the means for combining primitive constraints, 24| in order to express more complex relations. A connector is an object 25| that holds a value, that may participate in one or more constraints. 26| For example, in the above equation, there is a connector whose 27| value is the result of both sides of the equation. 28| That is, a single connector, say u, holds a value which is the value 29| of the result exits of the two constraints 30| (multiplier TWO A u), 31| and 32| (multiplier FIVE v u), where v is a connector whose 33| value is the result exit of the primitive constraint 34| (adder B w v) 35| where w is a connector whose value is the result exit of 36| (multiplier TWO A w), and also: 37| (constant 2 TWO) and (constant 5 FIVE). 38| Enforcing two constraints to share a result exit, is a way of 39| enforcing equality. 40| *** A CONSTRAINT NETWORK is a structure of primitive constraints, joined 41| by connectors. It can be drawn, graphically. 42| For example, for the relationship between Fahrenheit and centigrade 43| temperatures: 9 * C = 5 * (F - 32) 44| expressed only with the 3 primitive constraints described above, 45| we have the constraints network: 46| 47| __________ __________ __________ 48| C--------| m1 | | m1|----v-----| a1 | 49| ___ | * p-|---u---|-p * | | + s-|-----F 50| |9|---w---| m2 | | m2|--+ +--|-a2 | 51| ~~~ ~~~~~~~~~~ ~~~~~~~~~~ x y ~~~~~~~~~~ 52| | | 53| ___ ____ 54| |5| |32| 55| ~~~ ~~~~ 56| Here, the connectors are: C, w, u, v, x, y, F. There are 2 57| multiplication constraints that share their result exits, the 58| single adder shares its addend exit with the multiplier exit of 59| one of the multiplication constraints, and there are 60| 3 constant constraints. 61| 62| Computation in a constraint network is a propagation process. It 63| starts by giving a value to a connector. The connector then AWAKENS all 64| constraints to which it is connected (except for the constraint 65| that awakened it). Each awakened constraint checks if there is sufficient 66| information for determining a value for a connector associated with 67| the constraint. If so, the constraint sets the value of that connector, 68| and the computation proceeds in the same way. There is no global 69| supervisor (a main procedure) or a central clock to the computation: It 70| proceeds on the basis of propagating information to locally connected 71| objects. 72| 73| 74| Our simulation of the propagation of constraints process 75| assumes two main ADTs: connector and constraint. 76| For connector we have the constructor 'make-connector'. 77| For constraints we have various constructors, depending on the allowed 78| constraints. In the examples above, the constructors for constraints 79| are: adder, multiplier, and constant. 80| Using these constructors, the centigrade-fahrenheit network can be 81| built as follows: 82| 83| (define C (make-connector)) 84| (define F (make-connector)) 85| (centigrade-fahrenheit-converter C F) 86| 87| where: 88| (define (centigrade-fahrenheit-converter c f) 89| (let ((u (make-connector)) 90| (v (make-connector)) 91| (w (make-connector)) 92| (x (make-connector)) 93| (y (make-connector))) 94| (multiplier c w u) 95| (multiplier v x u) 96| (adder v y f) 97| (constant 9 w) 98| (constant 5 x) 99| (constant 32 y))) 100| 101| The way we wish to work with our system is as follows: 102| 103| > (set-value! C 25 'user) 104| done 105| > (get-value C) 106| 25 107| > (get-value F) 108| 77 109| > (set-value! F 212 'user) 110| error: Contradiction (77 212) 111| ;;; The value 25 of C enforces the value 77 for F. Hence, setting F 112| ;;; to 212 is a contradiction to its enforced value: 77. 113| 1> ^D 114| > (forget-value! F 'user) 115| () 116| > (get-value F) 117| 77 118| ;;; F's value derives from C's value. Hence, even if we enforce F to 119| ;;; forget its value, it immediately receives it again from C. 120| > (forget-value! C 'user) 121| done 122| > (set-value! C 100 'user) 123| done 124| > (get-value C) 125| 100 126| > (get-value F) 127| 212 128| > (forget-value! C 'user) 129| done 130| > (set-value! F 32 'user) 131| done 132| ;;; Now that C does not enforce any value, F can be set. 133| > (get-value C) 134| 0 135| > (get-value F) 136| 32 137| > (set-value! C 10 'user) 138| error: Contradiction (0 10) 139| 1> ^D 140| > (forget-value! C 'user) 141| () 142| > (set-value! C 10 'user) 143| error: Contradiction (0 10) 144| ;;; Same reason as for F above: C's value is derived from F's value. 145| ;;; Hence, it cannot be forgotten. 146| 1> ^D 147| > (forget-value! F 'user) 148| done 149| > (set-value! C 10 'user) 150| done 151| > (get-value C) 152| 10 153| > (get-value F) 154| 50 155| 156| It would be easier to watch the system if any time a connector 157| changes its value a message is printed. Since in a constraints 158| network there is no central "supervisor" that directs the computation, 159| but values PROPAGATE through constraints, we would like that connectors, 160| themselves will print their values, once they are changed. 161| The way this is accomplished in the system below is by introducing 162| a new kind of "constraint": A probe constraint (a kind of investigator). 163| A probe constraint can be attached to a single connector, and it has 164| the side effect that any time a new value propagates from the connector 165| to its related constraints (including the probe), a message about the 166| meaning of the connector and the new value is printed. 167| For the last Fahrenheit-Centigrade example, since the input output 168| connectors (the ones that appear in the equation described by the network) 169| are C and F, it makes sense to attach probes just to C and F: 170| 171| > (probe "centigrade temp" C) 172| Probe: centigrade temp = 10#[compound me] 173| ;;; This message is printed by the new probe that is attached to the 174| ;;; C connector: It prints the value of the connector, and its name. 175| ;;; The '#[compound me]' is the answer of the Scheme interpreter (the 176| ;;; value returned by probe is the procedural object attached to the 177| ;;; connector, and stands for the probe itself). 178| > (probe "Fahrenheit temp" F) 179| Probe: Fahrenheit temp = 50#[compound me] 180| 181| If we now repeat the session above, we will get messages about connectors 182| that change values: 183| > (set-value! C 25 'user) 184| error: Contradiction (10 25) 185| 1> ^D 186| > (get-value C) 187| 10 188| > (get-value F) 189| 50 190| > (forget-value! C 'user) 191| 192| Probe: centigrade temp = ? 193| ;;; The probe set on the C connector prints that message. The propagation 194| ;;; of the change in C's value reaches the adder, whose sum exit is the 195| ;;; F connector. The adder instructs the connector to forget its 196| ;;; value, and the change is printed by the probe: 197| Probe: Fahrenheit temp = ?done 198| > (set-value! C 100 'user) 199| 200| Probe: centigrade temp = 100 201| Probe: Fahrenheit temp = 212done 202| > (forget-value! C 'user) 203| 204| Probe: centigrade temp = ? 205| Probe: Fahrenheit temp = ?done 206| > (set-value! F 32 'user) 207| 208| Probe: Fahrenheit temp = 32 209| Probe: centigrade temp = 0done 210| > (set-value! C 25 'user) 211| error: Contradiction (0 25) 212| 1> ^D 213| > (set-value! F 212 'user) 214| error: Contradiction (32 212) 215| 1> ^D 216| > (forget-value! F 'user) 217| 218| Probe: Fahrenheit temp = ? 219| Probe: centigrade temp = ?done 220| 221| Implementing the Constraint System 222| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 223| 224| The constraints network is modeled by procedural objects that 225| stand for connectors and constraints. Connectors keep track, in their 226| local state, of constraints they are attached to, and constraints keep 227| track, in their local state, of connectors 228| attached to them. Both, connectors and constraints, receive messages 229| that drive their actions. 230| 231| The basic OPERATIONS for CONNECTORS, in addition to the constructor 232| 'make-connector', are: 233| (has-value? ): tells whether the connector has a value. 234| (get-value ): returns the connector's current value. 235| (set-value! ): tells the connector 236| that some informant is requesting it to set its value to a new one. 237| The informant can be either a constraint (to which the connector is 238| connected), or the user. 239| (forget-value! ): tells the connector that some 240| retractor is requesting it to forget its value. 241| The retractor can be either a constraint (to which the connector is 242| connected), or the user. 243| (connect ): tells the connector that it should 244| participate in a new constraint. 245| 246| The COMMUNICATION between connectors to constraints is performed 247| by two communication procedures that the connectors use to tell the 248| constraints about changes in their values: 249| (inform-about-value ): A connector applies that procedure to 250| let know that it has a new value. 251| (inform-about-no-value ): A connector applies that procedure 252| to let know that it has lost its value. 253| 254| Implementing the constraints: 255| ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 256| The constraints we implement here are: adder, multiplier, constant, 257| and probe. Each constraint is a procedural object that is driven 258| by the messages 'I-have-a-value' and 'I-lost-my-value', sent by its 259| connectors. Each constraint has, in its local state, local variables that 260| are set to its connectors, and private procedures for processing 261| new values, and forgetting values of connectors. 262| 263| The ADDER constraint: 264| 265| (define (adder a1 a2 sum) 266| (define (process-new-value) 267| (cond ((and (has-value? a1) (has-value? a2)) 268| (set-value! sum 269| (+ (get-value a1) (get-value a2)) 270| me)) 271| ((and (has-value? a1) (has-value? sum)) 272| (set-value! a2 273| (- (get-value sum) (get-value a1)) 274| me)) 275| ((and (has-value? a2) (has-value? sum)) 276| (set-value! a1 277| (- (get-value sum) (get-value a2)) 278| me)))) 279| 280| (define (process-forget-value) 281| (forget-value! sum me) 282| (forget-value! a1 me) 283| (forget-value! a2 me) 284| (process-new-value)) 285| 286| (define (me request) 287| (cond ((eq? request 'I-have-a-value) 288| process-new-value) 289| ((eq? request 'I-lost-my-value) 290| process-forget-value) 291| (else 292| (error "Unknown request -- ADDER" request)))) 293| 294| (connect a1 me) 295| (connect a2 me) 296| (connect sum me) 297| me) 298| 299| When an adder is first constructed (by the 'adder constructor), an 300| environment with a new frame is created. In that frame, there are local 301| variables that point to the adder's connectors, and internal procedures 302| for process-new-value, process-forget-value, and me, which IS the new 303| adder object itself. The 'me' procedure is returned as the adder's value. 304| When an adder (that is, a 'me' procedure) 305| is invoked with the 'I-have-a-value' message from one of 306| its connectors (using the inform-about-value procedure), its 307| 'process-new-value' procedure is called. This procedure checks if any 308| two of its connectors (found in the local state of the invoked adder) 309| have values, and in that case it sets the value of the third connector 310| accordingly. The informant argument to set-value! is always 'me', which is 311| the adder object itself. 312| When an adder is invoked with the 'I-lost-my-value' message from one of 313| its connectors (using the inform-about-no-value procedure), its 314| 'process-forget-value' procedure is called. This procedure first requests 315| all connectors to forget their values (but only those that were actually 316| set by that adder will be forgotten), and then calls 'process-new-value', 317| to see if there are still values around that need to be propagated. 318| For example, if a connector was set by the user, the adder cannot enforce 319| it to forget its value (see the Fahrenheit-centigrade example). 320| 321| The MULTIPLIER constraint: 322| 323| (define (multiplier m1 m2 product) 324| (define (process-new-value) 325| (cond ((or (if (has-value? m1) (= (get-value m1) 0) nil) 326| (if (has-value? m2) (= (get-value m2) 0) nil)) 327| (set-value! product 0 me)) 328| ((and (has-value? m1) (has-value? m2)) 329| (set-value! product 330| (* (get-value m1) (get-value m2)) 331| me)) 332| ((and (has-value? product) (has-value? m1)) 333| (set-value! m2 334| (/ (get-value product) (get-value m1)) 335| me)) 336| ((and (has-value? product) (has-value? m2)) 337| (set-value! m1 338| (/ (get-value product) (get-value m2)) 339| me)))) 340| 341| (define (process-forget-value) 342| (forget-value! product me) 343| (forget-value! m1 me) 344| (forget-value! m2 me) 345| (process-new-value)) 346| 347| (define (me request) 348| (cond ((eq? request 'I-have-a-value) 349| process-new-value) 350| ((eq? request 'I-lost-my-value) 351| process-forget-value) 352| (else 353| (error "Unknown request -- MULTIPLIER" request)))) 354| 355| (connect m1 me) 356| (connect m2 me) 357| (connect product me) 358| me) 359| 360| The CONSTANT constraint: 361| 362| A constant constraint is a simple procedural object with a single 363| connector. When a constant constraint is set (by calling the constant 364| procedure), a new environment, with a new frame is created. The single 365| connector and its value are kept in the local state, and the constant 366| constraint is represented by the local procedure 'me'. The new constant 367| constraint is connected to its connector, by calling the 'connect' 368| operator of connectors (which inserts the new constraint, represented by 369| the local procedure 'me', into a list of constraints to which the 370| connector is connected), and the value of the connector is set to the 371| local value of the new constant constraint. The informant for this change 372| is the constant constraint. 373| 374| (define (constant value connector) 375| (define (me request) 376| (error "Unknown request -- CONSTANT" request)) 377| (connect connector me) 378| (set-value! connector value me) 379| me) 380| 381| The PROBE constraint: 382| 383| A probe constraint exists just for the sake of printing output 384| messages. Hence, it is a procedural object with a single connector 385| kept in its local state. When a probe is attached to a connector, a new 386| frame is created, with binding to that connector, and internal 387| procedures for printing messages in case of setting a new value or 388| forgetting a value. The new probe constraint is represented by an internal 389| 'me' procedure, which is connected to the connector (kept in the local 390| state). 391| 392| (define (probe name connector) 393| (define (process-new-value) 394| (newline) 395| (princ "Probe: ") 396| (princ name) 397| (princ " = ") 398| (princ (get-value connector))) 399| 400| (define (process-forget-value) 401| (newline) 402| (princ "Probe: ") 403| (princ name) 404| (princ " = ") 405| (princ "?")) 406| 407| (define (me request) 408| (cond ((eq? request 'I-have-a-value) 409| process-new-value) 410| ((eq? request 'I-lost-my-value) 411| process-forget-value) 412| (else 413| (error "Unknown request -- PROBE" request)))) 414| 415| (connect connector me) 416| me) 417| 418| Implementing CONNECTORS: 419| 420| A connector is represented as a procedural object with three local 421| state variables: 422| value--the current value of the connector, 423| informant-- the object that set the connector's value, 424| constraints-- a list of constraints in which the connector participates. 425| When a connector is first set, a new environment, with a new frame is 426| created, and the three local variables are set to nil. That environment 427| has three local procedures, set-my-value, forget-my-value, and 428| connect bound in it, and a 'me' procedure that IS the connector. 429| A connector (that is a 'me' procedure) can be called (applied) either 430| with a request for information (whether it has a value, or what is the 431| value), or with a command. The message 'set-value' invokes the 432| local set-my-value procedure of the connector, which sets its value to 433| the new one, in case that there is no old value, and propagates the new 434| value to the related constraints (documented in the local variable 435| 'constraints'). The message 'forget' sets the local variable 'value' 436| to nil, in case that the informant value is exactly the retractor 437| argument, and again propagates the change to the related constraints. 438| The message 'connect' adds a new constraint to the local constraints 439| variable, and propagates the connector's value, if it is not null. 440| 441| (define (make-connector) 442| (let ((value nil) (informant nil) (constraints '())) 443| (define (set-my-value newval setter) 444| (cond ((not (has-value? me)) 445| (set! value newval) 446| (set! informant setter) 447| (for-each-except setter 448| inform-about-value 449| constraints)) 450| ((not (= value newval)) 451| (error "Contradiction" (list value newval))))) 452| 453| (define (forget-my-value retractor) 454| (if (eq? retractor informant) 455| (begin (set! informant nil) 456| (for-each-except retractor 457| inform-about-no-value 458| constraints)))) 459| 460| (define (connect new-constraint) 461| (if (not (memq new-constraint constraints)) 462| (set! constraints 463| (cons new-constraint constraints))) 464| (if (has-value? me) 465| (inform-about-value new-constraint))) 466| 467| (define (me request) 468| (cond ((eq? request 'has-value?) 469| (not (null? informant))) 470| ((eq? request 'value) value) 471| ((eq? request 'set-value!) set-my-value) 472| ((eq? request 'forget) forget-my-value) 473| ((eq? request 'connect) connect) 474| (else (error "Unknown operation -- CONNECTOR" 475| request)))) 476| me)) 477| 478| The propagation of a new connector's value to its related constraints 479| is accomplished via the following iterative procedure for-each-except, 480| which applies a designated procedure to all items in a list, except a 481| given one: 482| 483| (define (for-each-except exception procedure list) 484| (define (loop items) 485| (cond ((null? items) 'done) 486| ((eq? (car items) exception) (loop (cdr items))) 487| (else (procedure (car items)) 488| (loop (cdr items))))) 489| (loop list)) 490| 491| The communication between a connector to its related constraints is 492| accomplished via the following two procedures, that simply apply 493| a constraint to an appropriate message. This application returns 494| a procedure with no arguments, that is immediately applied. 495| 496| (define (inform-about-value constraint) 497| ((constraint 'I-have-a-value))) 498| 499| (define (inform-about-no-value constraint) 500| ((constraint 'I-lost-my-value))) 501| 502| Implementing the operators of the connectors: 503| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 504| 505| Since connectors are procedural objects, the operators of connectors 506| are just calls to the connectors themselves, with appropriate messages. 507| Each such call returns a local procedure of the connector, and 508| that procedure is immediately applied. 509| This way we guarantee that each operation on a connector applies the 510| appropriate local procedure of the connector argument. 511| 512| (define (has-value? connector) 513| (connector 'has-value?)) 514| 515| (define (get-value connector) 516| (connector 'value)) 517| 518| (define (forget-value! connector retractor) 519| ((connector 'forget) retractor)) 520| 521| (define (set-value! connector new-value informant) 522| ((connector 'set-value!) new-value informant)) 523| 524| (define (connect connector new-constraint) 525| ((connector 'connect) new-constraint))