;;;;;;;;;;;;;;;;;;;;;
;;;
;;; ACT-R model of experiment 1 in the
;;; Hayman and Tulving paper,
;;; as reported in chapter 7
;;;
;;;
;;; Requires ACT-R 4.0 
;;;
;;; to run the model call
;;; (do-ht-experiment n)
;;; where n is the number of runs

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; This section contains the LISP functions to simulate
;;; the experiment, implement the interface, collect the
;;; data, and display the results
;;;
;;; The ACT-R Model starts further down
;;;

;;; Global variables

(defvar *test1-words*)
(defvar *test2-words*)
(defvar *prebuffer-words*)
(defvar *postbuffer-words*)
(defvar *distract1-words*)
(defvar *distract2-words*)
(defvar *study-words*)
(defvar *stop-it*)
(defvar *answered*)
(defvar *which-test*)
(defvar *test-word*)
(defvar total-t1-st-cond)
(defvar total-t1-st-cond-shown)
(defvar total-t2-st-cond)
(defvar total-t2-st-cond-shown)
(defvar total-t2-st-cont)
(defvar total-t2-st-cont-shown)
(defvar total-t1-ns-cond)
(defvar total-t1-ns-cond-shown)
(defvar total-t2-ns-cond)
(defvar total-t2-ns-cond-shown)
(defvar total-t2-ns-cont)
(defvar total-t2-ns-cont-shown)
(defvar *correct-answer*)
(defvar *ht-low-ia*)
(defvar *ht-high-ia*)
(defvar *ht-pan*)
(defvar *ht-an*) 
(defvar *same-scores*)
(defvar *diff-scores*)
(defvar *ht-rt*)
(defvar *v*)
(defvar *factor*)
(defvar *penalty*)
(defvar *runs*)
(defvar *respond*)
(defvar *encode*)
(defvar *rehearse*)
(defvar *noise*)

(setf *respond* .5)
(setf *encode* .2)
(setf *rehearse* .5)

(setf *penalty* 15)
(setf *factor* .5)
(setf *ht-low-ia*  .4)
(setf *ht-high-ia* 3.8)
(setf *noise* .3)
(setf *ht-pan* (/ *noise* (sqrt 2)))
(setf *ht-an* (/ *noise* (sqrt 2))) 
(setf *ht-rt* -.45)
(setf *v* nil)
(setf *runs* 1)



(defparameter *letters-for-ht* #(A B C D E F G H I J K L M N O P Q R S T U V W X Y Z))
(defparameter *same-ht-data* '(.324 .168 .359 .198 .093 .183))
(defparameter *diff-ht-data* '(.353 .336 .369 .186 .191 .174))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; 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 "Hayman and Tulving Experiment model" 2)
        (:hidden :sy *ht-low-ia*  .4)
        (:hidden :sy *ht-high-ia* 3.8)
        (:table)
        
        (:table)
        "Noise (s): "   (:string :sy *noise* .3)             (:new-row)
        "Threshold: "                        (:string :sy *ht-rt* -.45)  (:new-row)
        "Scale factor (F): "                 (:string :sy *factor* .5)       (:new-row)
        "Mismatch Penalty:"                  (:string :sy *penalty* 1.5) (:new-row)
        "Response time (sec.): " (:string :sy *respond* .5)        (:new-row)
        "Encoding time (sec.): " (:string :sy *encode* .2)         (:new-row)
        "Rehearsal time (sec.): " (:string :sy *rehearse* .5) (:new-row)
        "Number of runs (1 - 20): "          (:string :sy *runs* 1)
        
        (:table-end)
        
        (:table)
        (:checkbox "Trace" :sy *v*  nil) (:new-row)
        (:table-end)
        
        (:table-end)
        
        (:new-para)
        (:button "Show Experiment Results" "(display-ht-results *same-ht-data* *diff-ht-data* nil)")
        (:new-para)
        (:button "Run model" "(progn 
                                (when (numberp *penalty*) 
                                   (setf *penalty* (* 10 *penalty*)))
                               (if (and (numberp *penalty*)
                                        (numberp *noise*) (numberp *ht-rt*) 
                                        (numberp *factor*) (numberp *runs*)
                                        (numberp *encode*) (numberp *rehearse*)
                                        (numberp *respond*))                  
                                       (progn
                                      (setf *ht-an* (/ *noise* (sqrt 2)))
                                      (setf *ht-pan* (/ *noise* (sqrt 2)))
                                      (do-ht-experiment (min 20 (max 1 *runs*)))) 
                                  (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 about 1 minute for 1 run of the model"
        (:new-line)
        "- The trace of 1 run is approximatly 350k (250 pages) in size"
        (:new-para)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; This section contains the LISP code
;;; to simulate the experiment, and 
;;; display the results

;;; generate-n-unique-pairs-ht takes one 
;;; parameter, the number of letter pairs to generate,
;;; and returns the list containig n distinct
;;; letter pairs

(defun generate-n-unique-pairs-ht (n)
  (do ((count 0)
       (pairs nil)
       (pair (cons (string-upcase (format nil "LET-~a" (aref *letters-for-ht* (random 26)))) (string-upcase (format nil "LET-~a" (aref *letters-for-ht* (random 26)))))
             (cons (string-upcase (format nil "LET-~a" (aref *letters-for-ht* (random 26)))) (string-upcase (format nil "LET-~a" (aref *letters-for-ht* (random 26)))))))
      ((= count n) pairs)
    (unless (member pair pairs :test #'equal)
      (setf pairs (cons pair pairs))
      (incf count))))

;;; generate-words-ht creates the chunks
;;; for the letters, sets the similarity 
;;; between each one and the - (used to represent 
;;; a missing letter) to 1
;;; then generates the 4 letter 'words' to use
;;; for the stimuli by combinding distinct pairs
;;; such that no two words have the same first and third letters
;;; or the same second and fourth letters
;;; the chunks for the words are then created and
;;; the ia between the word and its first letter is set to *high-ia*
;;; and the ia between the word and the rest of the letters 
;;; is set to *low-ia*
;;; next the list of words is broken into the separate
;;; lists for the different parts of the experiment,
;;; and finally the baselevel activations are set for the
;;; words and for the letters

(defun generate-words-ht ()
  (dotimes (i 26)
    (add-dm-fct (list (list (intern (string-upcase (format nil "LET-~a" (aref *letters-for-ht* i)))) 'isa 'letter)))
    (setsimilarities-fct (list (list (intern (string-upcase (format nil "LET-~a" (aref *letters-for-ht* i)))) '- 1))))
  (let ((words nil))
    (do ((1-3-pairs (generate-n-unique-pairs-ht 224) (cdr 1-3-pairs))
         (2-4-pairs (generate-n-unique-pairs-ht 224) (cdr 2-4-pairs)))
        ((null 1-3-pairs))
      (let* ((first-pair (car 1-3-pairs))
             (second-pair (car 2-4-pairs))
             (word (intern (string-upcase (format nil "~a~a~a~a" (subseq (car first-pair) 4) (subseq (car second-pair) 4)
                                   (subseq (cdr first-pair) 4) (subseq (cdr second-pair) 4))))))
        (add-dm-fct (list (list word 'isa 'word 'first (intern (car first-pair)) 'second (intern (car second-pair))
                                'third (intern (cdr first-pair)) 'fourth (intern (cdr second-pair)))) :reset-ia nil)


        (setia-fct (list (list word (intern (car second-pair)) *ht-low-ia*)))
        (setia-fct (list (list word (intern (cdr first-pair)) *ht-low-ia*)))
        (setia-fct (list (list word (intern (cdr second-pair)) *ht-low-ia*)))
        (setia-fct (list (list word (intern (car first-pair)) *ht-high-ia*)))
        (setia-fct (list (list word '- .1)))


        (setia-fct (list (list (intern (car second-pair))  word *ht-low-ia*)))
        (setia-fct (list (list  (intern (cdr first-pair)) word  *ht-low-ia*)))
        (setia-fct (list (list  (intern (cdr second-pair)) word  *ht-low-ia*)))
        (setia-fct (list (list (intern (car first-pair)) word  *ht-high-ia*)))
        (setia-fct (list (list  '- word .1)))

        (setf words (cons (list word (car first-pair) (car second-pair) (cdr first-pair)
                                (cdr second-pair) (random 2)) words))))
    (setf *test1-words* (subseq words 0 32))
    (setf *test2-words* (subseq words 32 64))
    (setf *prebuffer-words* (subseq words 64 80))
    (setf *postbuffer-words* (subseq words 80 160))
    (setf *distract1-words* (subseq words 160 192))
    (setf *distract2-words* (subseq words 192))
    (setf *study-words* (append *prebuffer-words* (permut-for-ht (append *test1-words* *test2-words*)) *postbuffer-words*))
    (setallbaselevels 8 -10000)
    
    (dotimes (i 26)
      (setbaselevels-fct (list (list (intern (string-upcase (format nil "LET-~a" (aref *letters-for-ht* i)))) 200 -1000))))))

;;; permut-for-ht takes one parameter
;;; a list, and returns the same list in 
;;; a random order

(defun permut-for-ht (lis)
  (do ((result  (list (nth (random (length lis)) lis))
                (cons (nth (random (length lis)) lis) result)))
      ((null lis) result)
    (setf lis (remove (car result) lis :count 1))
    (cond ((null lis) (return result)))))

;;; do-ht-experiment takes one parameter, the
;;; number of runs throught the experiment
;;; to simulate, and then runs n simulations
;;; in both the same and different condition
;;; and displays the results

(defun do-ht-experiment (n)
  (setf *answered* (make-array 2))
  (setf *same-scores* (do-one-implicit-ht n t))
  (setf *diff-scores* (do-one-implicit-ht n nil))
  (display-ht-results *same-scores* *diff-scores* t))

;;; do-one-implicit-ht takes 2 parameters, the
;;; numbr of simulations to run, and whether the
;;; condition is the same fragment (same = t) or a different
;;; fragment (same = nil)
;;; it returns a list of the accuracies
;;; for each type of fragment

(defun do-one-implicit-ht (n same)
  (setf total-t1-st-cond 0)
  (setf total-t1-st-cond-shown 0 )
  (setf total-t2-st-cond 0)
  (setf total-t2-st-cond-shown 0)
  (setf total-t2-st-cont 0)
  (setf total-t2-st-cont-shown 0)
  (setf total-t1-ns-cond 0)
  (setf total-t1-ns-cond-shown 0)
  (setf total-t2-ns-cond 0)
  (setf total-t2-ns-cond-shown 0)
  (setf total-t2-ns-cont 0)
  (setf total-t2-ns-cont-shown 0)
  
  (dotimes (i n)
    (run-implicit-ht-experiment same))
  
  (list  (/ total-t1-st-cond total-t1-st-cond-shown )
         (/ total-t2-st-cond total-t2-st-cond-shown)
         (/ total-t2-st-cont total-t2-st-cont-shown )
         (/ total-t1-ns-cond total-t1-ns-cond-shown )
         (/ total-t2-ns-cond total-t2-ns-cond-shown )
         (/ total-t2-ns-cont total-t2-ns-cont-shown )))
 

;;; set-implicit-ht-parameters 
;;; sets the ACT-R parameters for the simulation
;;; it is called prior to each run

(defun set-implicit-ht-parameters ()
  (sgp-fct (list :al nil :bll .5 :era t :pm t :mp *penalty* :ans *ht-an* :pas *ht-pan* :rt *ht-rt* :lf *factor* :v *v*))
  (parameters-fct 'complete-word (list :effort *respond*))
  (parameters-fct 'rehearse-word (list :effort *rehearse*))
  (parameters-fct 'attend (list :effort *encode*)))

;;; run-implicit-ht-experiment takes one parameter
;;; which specifies the type of fragment to display
;;; in test 2 for the incorrect responses from test 1
;;; if same is t then the same fragment is shown, if
;;; it is nil then the opposite fragment is shown
;;; first the list of words is studied,
;;; then the two test phases are run

(defun run-implicit-ht-experiment (same)
  (reset)
  (set-implicit-ht-parameters)
  (generate-words-ht)
  (setf *stop-it* 0) 
  (setf (aref *answered* 0) nil)
  (setf (aref *answered* 1) nil)
  (add-dm-fct '((goal isa study-words))  :reset-ia nil)
  
  (wmfocus goal) 
  
  (run)

  (actr-time 180)
  (setf *which-test* 0)
  
  (dolist (x (permut-for-ht (append *distract1-words* *test1-words*)))
    (if (= (nth 5 x) 0)
        (mod-chunk-fct 'newgoal  (list  'first (intern (nth 1 x)) 'second '- 'third (intern (nth 3 x)) 'fourth '- ))
        (mod-chunk-fct 'newgoal (list  'first '- 'second (intern (nth 2 x)) 'third '- 'fourth (intern (nth 4 x)) )))
    (wmfocus newgoal)
    (setbaselevels (newgoal 2))
    (setf *test-word* (car (no-output (swm-fct (list 'isa 'word 'first (intern (nth 1 x)) 'third (intern (nth 3 x)))))))
    
    (setf *correct-answer* nil)
    
    (run))
  
  (actr-time 180)
   
  (setf *which-test* 1)
  
  (let ((missed (missed-test1-for-ht)))
    
    (dolist (x (permut-for-ht (append *distract2-words* *test2-words* (missed-test1-for-ht))))
      (if (member x missed :test #'equal)
          (if same
              (if (= (nth 5 x) 0)
                  (mod-chunk-fct 'newgoal (list  'first (intern (nth 1 x)) 'second '- 'third (intern (nth 3 x)) 'fourth '- ))
                  (mod-chunk-fct 'newgoal (list  'first '- 'second (intern (nth 2 x)) 'third '- 'fourth (intern (nth 4 x)) )))
              (if (= (nth 5 x) 0)
                  (mod-chunk-fct 'newgoal (list  'first '- 'second (intern (nth 2 x)) 'third '- 'fourth (intern (nth 4 x)) ))
                  (mod-chunk-fct 'newgoal (list  'first (intern (nth 1 x)) 'second '- 'third (intern (nth 3 x)) 'fourth '- ))))
          (if (= (nth 5 x) 0)
              (mod-chunk-fct 'newgoal (list  'first (intern (nth 1 x)) 'second '- 'third (intern (nth 3 x)) 'fourth '- ))
              (mod-chunk-fct 'newgoal (list  'first '- 'second (intern (nth 2 x)) 'third '- 'fourth (intern (nth 4 x)) ))))
      (wmfocus newgoal)
      (setbaselevels (newgoal 2))
      (setf *test-word* (car (no-output (swm-fct (list 'isa 'word 'second (intern (nth 2 x)) 'fourth (intern (nth 4 x)))))))
      (setf *correct-answer* nil)

      (run)))
  (stats-for-ht))

;;; get-word-for-ht takes no parameters,
;;; and sets the stopping time, and returns the next word

(defun get-word-for-ht (goal)
  (let ((word (pop *study-words*)))
    (setf *stop-it* (+ 4.0 *stop-it*))
;    (modwme-fct goal (list 'word (car word)))
    (car word)))
 
;;; missed-test1-for-ht takes no parameters
;;; and returns the list of words that were 
;;; answered incorrectly on the first test
 
(defun missed-test1-for-ht ()
  (let ((test1-words (append *test1-words* *distract1-words*)))
    (dolist (x (aref *answered* 0) test1-words)
      (when (member x test1-words :key #'car :test #'eq)
        (setf test1-words (remove x test1-words :key #'car :test #'eq :count 1))))))

;;; count-words-for-ht takes 2 parameters
;;; takes 2 lists of words, and returns the 
;;; number of words in the first list that are 
;;; in the second list

(defun count-words-for-ht (words which-list)
  (let ((n 0))
    (dolist (x words n)
    (when (member x which-list :key #'car :test #'eq)
      (incf n)))))

;;; stats-for-ht takes no parameters,
;;; and increments the global variables for the 
;;; statistics of the experiment

(defun stats-for-ht ()
  (let* ((t1-st-cond-count (count-words-for-ht (aref *answered* 0) *test1-words*))
         (t1-ns-cond-count (count-words-for-ht (aref *answered* 0) *distract1-words*)))
 
    (incf total-t1-st-cond t1-st-cond-count)
    (incf total-t1-st-cond-shown 32)
    (incf total-t2-st-cond (count-words-for-ht (aref *answered* 1) *test1-words*))
    (incf total-t2-st-cond-shown (- 32 t1-st-cond-count))
    (incf total-t2-st-cont (count-words-for-ht (aref *answered* 1) *test2-words*))
    (incf total-t2-st-cont-shown 32)
    (incf total-t1-ns-cond t1-ns-cond-count)
    (incf total-t1-ns-cond-shown 32)
    (incf total-t2-ns-cond (count-words-for-ht (aref *answered* 1) *distract1-words*))
    (incf total-t2-ns-cond-shown (- 32 t1-ns-cond-count))
    (incf total-t2-ns-cont (count-words-for-ht (aref *answered* 1) *distract2-words*))
    (incf total-t2-ns-cont-shown 32)))

;;; ht-exp-response takes one parameter
;;; a word, and adds that word to the proper list of 
;;; correctly answered words, and sets the *correct-answer*
;;; flag to t

(defun ht-exp-response (word)
  (setf *correct-answer* t)
  (setf (aref *answered* *which-test*) (cons word (aref *answered* *which-test*))))


;;; display-ht-results takes three parameters,
;;; the data for the same condition, the data for the
;;; different condition, and wheter the data is for the
;;; simulation or not, and displays a table with
;;; the results listed as percentages

(defun display-ht-results (same-data diff-data simulation)
  (when simulation 
    (format *standard-output* "~%~%Parameters for run: (~S ~S ~S ~S ~S ~S ~S ~S)~%"
            *noise* *ht-rt* *factor* *penalty* *respond* *encode* *rehearse* *runs*))

  (format *standard-output* "~%~a data:~%~%" (if simulation "Simulation" "Experimental"))

  (format *standard-output* "                   Studied Words        Nonstudied Words~%")
  (format *standard-output* "                 Test 1     Test 2     Test 1     Test 2~%")
  (format *standard-output* "                              Same fragments~%~%")
  (format *standard-output* "Conditional      ~4,1F%      ~4,1F%      ~4,1F%     ~4,1F%~%" (* 100 (nth 0 same-data)) (* 100 (nth 1 same-data)) (* 100 (nth 3 same-data)) (* 100 (nth 4 same-data)))
  (format *standard-output* "Control                     ~4,1F%                ~4,1F%~%~%" (* 100 (nth 2 same-data)) (* 100 (nth 5 same-data)))
  (format *standard-output* "                           Different fragments~%~%")
  (format *standard-output* "Conditional      ~4,1F%      ~4,1F%      ~4,1F%     ~4,1F%~%" (* 100 (nth 0 diff-data)) (* 100 (nth 1 diff-data)) (* 100 (nth 3 diff-data)) (* 100 (nth 4 diff-data)))
  (format *standard-output* "Control                     ~4,1F%                ~4,1F%~%~%" (* 100 (nth 2 diff-data)) (* 100 (nth 5 diff-data))))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; This section contains the ACT-R productions
;;; and memory definitions for the simulation

(clearall)

;; chunks for study items
      
(chunk-type word first second third fourth)

(chunk-type letter)

;; chunks for goals 

(chunk-type study-words word)

(chunk-type rehearse-word word)

(chunk-type complete-words first second third fourth)


(add-dm (- isa letter)
        (newgoal isa complete-words))


;;; productions

(p attend
"
  IF the goal is to study words
     and the time has not expired
     and there are words to study
  THEN create and push a  goal to 
     rehearse the current word
"
  =goal>
     isa study-words
      
  !eval! (>= (actr-time) *stop-it*)

  !eval! *study-words*
==>
  =newgoal>
     isa rehearse-word
     word (!eval! (get-word-for-ht =newgoal))
 
;     !eval! (get-word-for-ht =newgoal)
  
   !push! =newgoal
)

(parameters-fct 'attend (list :effort *encode*))


(p rehearse-word
"
  IF the goal is to rehearse a word
     and the word and all of its
     letters can be retrieved
     and there is still time to rehearse it
  THEN report that it is being rehearsed
"
   =goal>
      isa rehearse-word
      word =word

   =word>
      isa word
      first =f
      second =s
      third =t
      fourth =fo
   =f>
      isa letter

   =s>
      isa letter

   =t>
      isa letter

   =fo>
      isa letter

   !eval! (< (actr-time) *stop-it*)

==>
   !output! ("Rehearsing ~S.~%" =word)
)

(parameters-fct 'rehearse-word (list :effort *rehearse*))


(p rehearse-skip
"
  IF the goal is to rehearse a word
  THEN do nothing
"
   =goal>
      isa rehearse-word
   
   !eval! (< (actr-time) *stop-it*)
==>
)

(parameters rehearse-skip :r .5)


(p done-rehearsing
"
  IF the goal is to rehearse a word
     and the time has passed 
  THEN pop the goal
"
   =goal>
      isa rehearse-word

   !eval! (>= (actr-time) *stop-it*)
==>
   !pop!
)


(p complete-word
"
  IF the goal is to complete a word
     and the letters and a word
     composed of those letters
     can be retrieved 
     and the word matches the
     presented word
  THEN respond with the word
     and pop the goal
"
   =goal>
      isa complete-words
      first =f
      second =s
      third =t
      fourth =fo

   =f>
      isa letter

   =s>
      isa letter

   =t>
      isa letter

   =fo>
      isa letter

   =word>
      isa word
      first =f
      second =s
      third =t
      fourth =fo

   !eval! (eq =word *test-word*)
==>
   !output! ("completes word as ~S~%" =word)

   !eval! (ht-exp-response =word)

   !pop!
)

(parameters-fct 'complete-word (list :effort *respond*))


(p cant-complete
"
  IF the goal is to complete a word
  THEN pop the goal
"
   =goal>
      isa complete-words
==>
   !pop!
)

(parameters cant-complete :r .5 :effort 15)