;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; ACT-R Button Press Model
;;; works with ACT-R 4.0 (2/6/97 or newer)
;;;
;;; coded by: Dan Bothell
;;; 
;;; This file contains an ACT-R model
;;; of button choice, which can be used to
;;; simulate many different types of 
;;; choice experiemnts.
;;;
;;; A command line interface, 
;;; and a WWW interface are included.
;;;
;;; To use the command line interface,
;;; set the parameters with setf, and then
;;; call (button-press-exp n) to simulate n button
;;; presses. 
;;;
;;;
;;; To use the WWW interface, you need to run
;;; the ACT-R on the Web application (follow the
;;; instructions provided with it), or use a
;;; web browser to connect to a site that has
;;; the model installed.


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Global Variables 
   

(defvar *act-r-button-press* 1)   ;; set by the model to the current button pressed (0 for button 1, 1 for button 2)
(defvar *correct-button*)         ;; the current correct button (0 for button 1, 1 for button 2)

;;; the rest are the parameters for the model, and are set by the interfaces

(defvar *button-1-p* .5)
(defvar *choice-g* 1)
(defvar *egn* .69)
(defvar *v* nil)
(defvar *button-1-r* .5)
(defvar *button-2-r* .5)
(defvar *button-1-q* 1)
(defvar *button-2-q* 1)
(defvar *button-1-a* .05)
(defvar *button-2-a* .05)
(defvar *button-1-b* 0)
(defvar *button-2-b* 0)
(defvar *button-press-runs* 1)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; This section contains the interface for the WWW using the
;;; ACT-R on the Web application by Elmar Schwarz


(defvar *WWW-interface*)

(setf  *WWW-interface* 
      '((:heading "Probability Learning Experiments Model" 2)
        (:table)
        
        (:table)
        "Probabilitiy button 1 correct: "       (:string :sy *button-1-p*   0.5)  (:new-row) (:new-row)
        "q choose-button-1 production:"         (:string :sy *button-1-q*   1)    (:new-row)
        "r choose-button-1 production:"         (:string :sy *button-1-r*   0.5)  (:new-row)
        "a choose-button-1 production:"         (:string :sy *button-1-a*    .05)  (:new-row)
        "b choose-button-1 production:"         (:string :sy *button-1-b*   0)    (:new-row) (:new-row)
        "q choose-button-2 production:"         (:string :sy *button-2-q*   1)    (:new-row)
        "r choose-button-2 production:"         (:string :sy *button-2-r*   0.5)  (:new-row)
        "a choose-button-2 production:"         (:string :sy *button-2-a*    .05)  (:new-row)
        "b choose-button-2 production:"         (:string :sy *button-2-b*   0)    (:new-row) (:new-row)
        "Noise variance (sigma squared): "              (:string :sy *egn*          .69)  (:new-row)
        "G: "                                   (:string :sy *choice-g*            1)    (:new-row)
        "Number of presses (max 10000): "       (:string :sy *button-press-runs* 1)
        (:table-end)
        
        (:table)
        
        (:checkbox "Trace"            :sy *v*  nil)   (:new-row)
        
        (:table-end)
        (:table-end)
        
        (:new-para)
        (:button "Run model" "(if (and (numberp *button-1-p*) (numberp *button-1-r*) (numberp *button-1-a*) 
                                       (numberp *button-1-b*) (numberp *button-1-q*) (numberp *button-2-r*) 
                                       (numberp *button-2-a*) (numberp *button-2-b*) (numberp *button-2-q*) 
                                       (numberp *choice-g*) (numberp *egn*) (numberp *button-press-runs*))
                                  (button-press-exp (min 10000 (max *button-press-runs* 1)))
                                  (format *standard-output* \"All parameters must be numbers.~%\"))")
        (:reset "Default values")
        (:button "Production Rules" "(let ((prods (no-output (pp))))
                                       (dolist (x prods)
                                         (pp-fct (list x))
                                         (spp-fct (list x))
                                         (format *standard-output* \"~%\")))")

        (:button "Chunk types" "(chunk-type)")
        (:button "Chunks" "(dm)")
        (:new-para)
         "TIME and SIZE:"
        (:new-para)
        "- It usually takes less than 1 minute for a run of 1000 presses"
        (:new-line)
        "- The trace of 1 run of 1000 presses is approximatly 150k (100 pages) in size"))



;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; button-press-exp takes one parameter which specifies how many button presses to 
;;; perform.  First, the ACT-R parameters are set according to the values given in
;;; the global variables (as set by the interface). Then, the correct button is chosen (based 
;;; on the probability set by the *button-1-p* global variable), and
;;; the model is called to guess (it sets whether it was a success or
;;; failure).  
;;; After all of the button presses have been done a summary table of the 
;;; correct buttons and guesses is printed out.

(defun button-press-exp (n)
  (let ((count (make-array '(2 2) :initial-element 0))) ;; an array to record presses and guesses
    
    (reset)
    
    (sgp-fct (list :ol t
                   :era t
                   :er t
                   :g  *choice-g*
                   :egn *egn*
                   :v *v*))
    
    (parameters-fct 'choose-button-1 
                    (list
                     :r *button-1-r* 
                     :q *button-1-q* 
                     :a *button-1-a* 
                     :b *button-1-b*))
    
    (parameters-fct 'choose-button-2 
                    (list
                     :r *button-2-r* 
                     :q *button-2-q* 
                     :a *button-2-a* 
                     :b *button-2-b*))
    
    (dotimes (i n)
      (setf *correct-button* (correct-button-press))
      
      (wmfocus goal)
      (run)
      
      (incf (aref count *correct-button* *act-r-button-press*)))
    
    (format *standard-output* "~%~%Simulation parameters: (~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S ~S )"
            *button-1-p* *button-1-q* *button-1-r* *button-1-a* *button-1-b* *button-2-q* 
            *button-2-r* *button-2-a* *button-2-b* *egn* *choice-g* *button-press-runs*)


    (format *standard-output* "~%~%                Button Pressed~%")
    (format *standard-output* "                   1      2~%")
    (format *standard-output* "Correct Button~%")
    (format *standard-output* "       1         ~4d   ~4d   ~4d~%" 
            (aref count 0 0) (aref count 0 1) (+ (aref count 0 0) (aref count 0 1)))
    
    (format *standard-output* "       2         ~4d   ~4d   ~4d~%" 
            (aref count 1 0) (aref count 1 1) (+ (aref count 1 0) (aref count 1 1)))
    
    (format *standard-output* "                 ~4d   ~4d ~%" 
            (+ (aref count 0 0) (aref count 1 0)) (+ (aref count 0 1) (aref count 1 1)))))

;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; correct-button-press returns a value representing the correct button with 
;;; probability of button 1 being correct equal to *button-1-p*.  A 0 represents
;;; button 1, and a 1 represents button 2.

(defun correct-button-press ()
  (if (< (random 1.0) *button-1-p*)
    0
    1))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; The rest of the file contains the 
;;; actual ACT-R model to guess the button to press.
;;; There are 2 productions, one to press button 1, and
;;; one to press button 2.  When the goal is set to
;;; press a button, either one can fire.  The one which
;;; fires is based on the (noisy) PG-C values for the productions.
;;;
   
(clearall)

(sgp :g 1.0 :era t :egn .5 :v nil :pl nil)

(chunk-type press-button "The type for the only goal")

(add-dm (goal 
        "The goal is to press a button" 
        isa press-button))


(p choose-button-1
" 
   IF the goal is to press a button 
   THEN press button 1
      and pop the goal
"
   =goal>
      isa press-button
==>
  !output! ("~%pressed: button 1  correct button: ~S~%" (if (zerop *correct-button*) 'button-1 'button-2))

  !eval! (setf *act-r-button-press* 0)

  !pop!
)

(parameters choose-button-1 :r .5 :q 1.0 :a .2 :b 0)


(p choose-button-2
" 
   IF the goal is to press a button
   THEN press button 2
      and pop the goal
"
   =goal>
      isa press-button
==>
   !output! ("~%pressed: button 2  correct button: ~S~%" (if (zerop *correct-button*) 'button-1 'button-2))

   !eval! (setf *act-r-button-press* 1)

   !pop!
)

(parameters choose-button-2 :r .5 :q 1.0 :a .2 :b 0)