;;;
;;; =====
;;; Model
;;; =====

;;; Parameters

(defvar *ans* 0.25)
(defvar *rt* -3.75)
(defvar *lf* 0.125)
(defvar *mp* 1.5)
(defvar *io* 0.2)
(defvar *count-set-ratio* 10.0)
(defvar *count-delay-ratio* 1.0)

(defun output-parameters (&optional (output t))
  (format output "~%ANS: ~6,3F~CRT: ~6,3F~CLF: ~6,3F~CMP: ~6,3F~%~
                   IO: ~6,3F~CCOUNT SET: ~6,3F~CCOUNT DELAY: ~6,3F~%"
          *ans* #\tab *rt* #\tab *lf* #\tab *mp*
          *io* #\tab *count-set-ratio* #\tab *count-delay-ratio*))

;;; Odds conversion utility

(defun display-odds (probabilities &optional (ratio 1.0) (format "~6,3F"))
  (dolist (probability probabilities)
    (format t "~?~%" format (list (/ 1.0 (- (/ ratio probability) 1.0))))))

;;; Frequency tables

(defun output-frequency-table (frequencies &optional (format " ~5,3F"))
  "Prints a two-dimensional square array of frequencies."
  (format t "FREQUENCIES:~%")
  (let ((n (array-dimension frequencies 0)))
    (dotimes (i n)
      (dotimes (j n)
        (format t format (aref frequencies i j)))
      (format t "~%"))))

(defun linear-frequency (n delta)
  (let ((frequencies (make-array n :initial-element 1.0)))
    (dotimes (i n frequencies)
      (decf (aref frequencies i) (* delta (- i (/ (1- n) 2)))))))

(defun even-frequency-table (n delta)
  "Generates the frequency table for a n x n table.
   Delta specifies the constant frequency differential
   between adjacent problems, as an absolute percentage."
  (let ((frequencies (make-array (list n n) :initial-element 0.0))
        (average (/ 1.0 n n)))
    (dotimes (i n)
      (dotimes (j n)
        (setf (aref frequencies i j)
              (+ average (* delta (- n 1 i j))))))
    frequencies))

;;; Problem sets

(defun generate-linear-problems (m n frequencies)
  "Generates a set of m problems to n frequencies."
  (let ((set nil)
        (probabilities 0.0)
        (problems 0))
    (dotimes (i n)
      (incf probabilities (aref frequencies i))
      (let ((copies (- (round (* probabilities (/ m n))) problems)))
        (setf set (nconc set (make-list copies :initial-element i)))
        (incf problems copies)))
    set))

(defun generate-problems (m n frequencies)
  "Generates a set of m problems to n x n frequencies."
  (let ((set nil)
        (probabilities 0.0)
        (problems 0))
    (dotimes (i n)
      (dotimes (j n)
        (incf probabilities (aref frequencies i j))
        (let ((copies (- (round (* probabilities m)) problems)))
          (setf set (nconc set
                           (make-list copies :initial-element
                                      (cons i j))))
          (incf problems copies))))
    set))

(defun select-linear-problem (n set)
  "Selects a problem among n in set."
  (let ((problem (nth (random n) set)))
    (values problem (delete problem set :test #'eq :count 1))))

(defun select-problem (n set)
  "Selects a problem among n in set."
  (let ((problem (nth (random n) set)))
    (values (car problem) (cdr problem)
            (delete problem set :test #'eq :count 1))))

;;; Corrections

(defun correct-cycles (correct)
  "Determines the number of corrections from the value of correct."
  (cond ((numberp correct)
         (multiple-value-bind (cycles probability)
                              (truncate correct)
           (+ cycles (if (< (random 1.0) probability) 1 0))))
        (correct 1)
        (t 0)))

;;; Numbers, facts, and how to access them

(defvar *numbers* (make-array 100 :initial-element nil)
  "Caches the two-digit numbers.")

(defun get-number (i)
  "Returns the chunk for number i < 100.  Gets it from the cache *numbers*
   or extracts it from declarative memory.  Creates it if necessary."
  (or (aref *numbers* i)
      (no-output
       (let ((number (first (sdm-fct (list 'isa 'number 'value i)))))
         (unless number
           (setf number (first (add-dm-fct
                                (list (list (intern (format nil "N~D" i))
                                            'isa 'number
                                            'tens (get-number (truncate i 10))
                                            'units (get-number (mod i 10))
                                            'value i))
                                :reset-ia nil))))
         (setf (aref *numbers* i) number)))))

(defun get-number-value (number)
  "Given a chunk returns the associated number, or number if it is a number."
  (when number (chunk-slot-value-fct number 'value)))

(defvar *count-facts* (make-array 20 :initial-element nil)
  "Caches the count facts.")

(defun get-count-fact (i)
  "Returns the counting fact for i->i+1.  Gets it from the cache *count-facts*
   or extracts it from declarative memory.  Creates it if necessary." 
  (or (aref *count-facts* i)
      (no-output
       (let ((fact (first (sdm-fct (list 'isa 'count 'from (get-number i))))))
         (unless fact
           (setf fact (first (add-dm-fct (list (list (gentemp "C") 'isa 'count
                                                     'from (get-number i)
                                                     'to (get-number (1+ i))))
                                         :reset-ia nil))))
         (setf (aref *count-facts* i) fact)))))

(defvar *addition-facts* (make-array '(10 10) :initial-element nil)
  "Caches the single-digit addition table.")

(defun get-addition-fact (i j)
  "Returns the correct addition fact for i+j.  Gets it from the cache
   *addition-facts* or extracts it from declarative memory.
   Creates it if necessary."
  (or (aref *addition-facts* i j)
      (no-output
       (let* ((fact-list (list 'isa 'arithmetic
                               'first (get-number i)
                               'operator '+
                               'second (if (= i j) 'double
                                           (get-number j))
                               'result (get-number (+ i j))))
              (fact (first (sdm-fct fact-list))))
         (unless fact
           (setf fact (first (add-dm-fct (list (cons (gentemp "A") fact-list))
                                         :reset-ia nil))))
         (setf (aref *addition-facts* i j) fact)))))

(defvar *multiplication-facts* (make-array '(10 10) :initial-element nil)
  "Caches the single-digit multiplication table.")

(defun get-multiplication-fact (i j)
  "Returns the correct multiplication fact for i*j.  Gets it from the cache
   *multiplication-facts* or extracts it from declarative memory.
   Creates it if necessary."
  (or (aref *multiplication-facts* i j)
      (no-output
       (let* ((fact-list (list 'isa 'arithmetic
                               'first (get-number i)
                               'operator '*
                               'second (if (= i j) 'double
                                           (get-number j))
                               'result (get-number (* i j))))
              (fact (first (sdm-fct fact-list))))
         (unless fact
           (setf fact (first (add-dm-fct (list (cons (gentemp "A") fact-list))
                                         :reset-ia nil))))
         (setf (aref *multiplication-facts* i j) fact)))))

(defun reset-facts ()
  (setf *numbers* (make-array 100 :initial-element nil))
  (setf *count-facts* (make-array 20 :initial-element nil))
  (setf *addition-facts* (make-array '(10 10) :initial-element nil))
  (setf *multiplication-facts* (make-array '(10 10) :initial-element nil)))

(reset-facts)

;;; Number similarities

(defun ratio-similarity (value1 value2)
  (if (and (zerop value1) (zerop value2)) 1.0
      (coerce (/ (min value1 value2) (max value1 value2)) 'float)))

(defvar *similarity-function* 'ratio-similarity)

(defun set-number-similarities (&optional (numbers 10))
  (no-output
   (let ((similarities nil))
     (dotimes (first numbers)
       (dotimes (second numbers)
         (let ((similarity (funcall *similarity-function* first second)))
           (when similarity
             (push (list (get-number first) (get-number second) similarity)
                   similarities)))))
     (set-similarities-fct similarities))))

;;; Counting facts history

(defun set-count-focused-differential-history (&key (repeat 1) (delay 1)
                                                    (difference 0.05) (n 19))
  (no-output
   (let ((frequency (linear-frequency n difference)))
     (dotimes (i n)
       (rehearse-chunk-fct (list (list (get-count-fact i) (get-number i)))
                           :repeat (* repeat (aref frequency i))
                           :force t :cycle 1 :time delay)))))

(defun set-count-unfocused-differential-history (&key (repeat 1) (delay 1)
                                                      (difference 0.05) (n 19))
  (no-output
   (let ((frequency (linear-frequency n difference)))
     (dotimes (i n)
       (rehearse-chunk-fct (list (list (get-count-fact i)
                                       (get-number i) (get-number (1+ i))))
                           :repeat (* repeat (aref frequency i))
                           :force t :cycle 1 :time delay)))))

;;; Count sampling

(defun deterministic-count-sampling (n &key (output t))
  (no-output
   (format output "~%Count Retrievals:~%")
   (let* ((results (make-array (list n (1+ n)) :initial-element 0.0))
          (rt (first (sgp :rt)))
          (noise (first (sgp :ans)))
          (temperature (* (sqrt 2.0) noise)))
     (sgp :ans nil)
     (unless (dm test-count)
       (add-dm-fct '((test-count isa count)) :reset-ia nil))
     (goal-focus test-count)
     (dotimes (i n)
       (let ((sum (exp (/ rt temperature)))
             (number (get-number i)))
         (mod-focus-fct (list 'from number))
         (setf (aref results i n) sum)
         (dotimes (j n)
           (let* ((count (get-count-fact j))
                  (prob (exp (/ (- (first (first (sdp-fct (list count :activation))))
                                   (* *mismatch-penalty*
                                      (- 1.0 (similarity-fct number
                                                             (chunk-slot-value-fct count 'from)))))
                                temperature)))
                  (to (chunk-slot-value-fct count 'to)))
             (when to (setf to (get-number-value to)))
             (incf (aref results i (or to n)) prob)
             (incf sum prob)))
         (dotimes (j (1+ n))
           (setf (aref results i j) (/ (aref results i j) sum))
           (format output "~5,1F~C" (* 100 (aref results i j)) #\tab))
         (format output "~%")))
     (sgp-fct (list :ans noise))
     (unless output (values results)))))

;;; Count training

(defstruct count from answer latency retrievals)

(defvar *count-feedback* t)

(defvar *count-answer* nil)

(defvar *count-retrievals* 0)

(defun get-count-result (from)
  (get-number (1+ (get-number-value from))))

(defun count-problem (i)
  (no-output
   (setf *count-answer* nil)
   (setf *count-retrievals* 0)
   (let ((goal (first (add-dm-fct (list (list (gentemp "C") 'isa 'count
                                              'from (get-number i)))
                                  :reset-ia nil))))
     (goal-focus-fct (list goal))
     (let ((latency (run 1)))
       (setf *count-answer* (get-number-value *count-answer*))
       (make-count :from i :answer *count-answer* :latency latency
                   :retrievals *count-retrievals*)))))

(defun analyze-count-errors (counts set n &key (output t))
  (let ((total-retrievals (length counts))
        (total-errors 0)
        (odds 0.0)
        (totals (make-array n :initial-element 0))
        (errors (make-array n :initial-element 0)))
    (dolist (count counts)
      (let ((from (count-from count))
            (answer (count-answer count)))
        (incf (aref totals from) 1)
        (unless (and (numberp answer) (= answer (1+ from)))
          (incf total-errors 1)
          (incf (aref errors from) 1))))
    (setf odds (/ (- set (- total-retrievals total-errors))
                  (- total-retrievals total-errors)))
    (format output "COUNT RETRIEVALS: ~D~CERRORS: ~D~C PERCENTAGE: ~6,1F~CLOG ODDS: ~6,3F~%ERRORS:"
            total-retrievals #\tab total-errors #\tab
            (* 100.0 (/ total-errors total-retrievals)) #\tab
            (if (= odds 0.0) odds (log odds)))
    (dotimes (from n)
      (format output " ~3D" (aref errors from)))
    (format output "~%PERCNT:")
    (dotimes (from n)
      (format output " ~3D" (if (zerop (aref totals from)) 0
                                (round (* 100 (/ (aref errors from)
                                                 (aref totals from)))))))
    (format output "~%")
    total-retrievals))

(defun count-problems (&key (difference 0.05) (n 19)
                            (delay 1500.0) (runs 10) (set 1000)
                            (reset t) (output t))
  (output-parameters output)
  (when reset (reset) (eval reset) (reset-facts))
  (no-output
   (let ((problems (generate-linear-problems set n (linear-frequency n difference))))
     (dotimes (run runs)
       (format output "~%COUNT RUN ~D~%" (1+ run))
       (let ((problems (copy-seq problems))
             (from nil)
             (result nil)
             (results nil))
         (dotimes (problem set)
           (multiple-value-setq (from problems)
             (select-linear-problem (- set problem) problems))
           (setf result (count-problem from))
           (unless (zerop (count-retrievals result))
             (push result results))
           (when delay (actr-time-fct delay)))
         (analyze-count-errors results set n :output output))))))

;;; Run a single addition problem

(defstruct problem first operator second answer latency retrievals computations)

(defvar *feedback* t)

(defvar *addition-answer* nil)

(defvar *repeated-answer* nil)

(defvar *multiplication-answer* nil)

(defvar *addition-retrievals* 0)

(defvar *multiplication-retrievals* 0)

(defvar *addition-computations* 0)

(defvar *multiplication-computations* 0)

(defun get-addition-result (first second)
  (get-number (+ (get-number-value first)
                 (get-number-value (if (eq second 'double) first second)))))

(defun addition-problem (i j &optional (max -1))
  (no-output
   (setf *addition-answer* nil)
   (setf *addition-retrievals* 0)
   (setf *addition-computations* 0)
   (let ((goal (first (add-dm-fct
                       (list (list (gentemp "A") 'isa 'arithmetic
                                   'first (get-number i)
                                   'operator '+
                                   'second (get-number j)))
                       :reset-ia nil))))
     (goal-focus-fct (list goal))
     (let ((latency (run-fct max)))
       (clear-goal-stack)
       (setf *addition-answer* (get-number-value *addition-answer*))
       (make-problem :first i :operator '+ :second j
                     :answer *addition-answer* :latency latency
                     :retrievals *addition-retrievals*
                     :computations *addition-computations*)))))

;;; Analyze errors and latencies of a list of problems

(defun analyze-errors (problems n &key (operator '+) (output t))
  (let ((total-errors 0)
        (totals (make-array (list (1+ n) (1+ n)) :initial-element 0))
        (errors (make-array (list (1+ n) (1+ n)) :initial-element 0))
        (retrievals 0)
        (computations 0))
    (format output "~%Errors: ")
    (dolist (problem problems)
      (let ((first (problem-first problem))
            (second (problem-second problem))
            (answer (problem-answer problem)))
        (incf (aref totals first second) 1)
        (unless (and (numberp answer) (= answer (funcall operator first second)))
          (incf total-errors 1)
          (incf (aref errors first second) 1))
        (incf retrievals (problem-retrievals problem))
        (incf computations (problem-computations problem))))
    (format output "~D~%" total-errors)
    (dotimes (first n)
      (dotimes (second n)
        (format output "~3D~C" (aref errors first second) #\tab)
        (incf (aref totals n second) (aref totals first second))
        (incf (aref totals first n) (aref totals first second))
        (incf (aref errors n second) (aref errors first second))
        (incf (aref errors first n) (aref errors first second)))
      (unless (zerop (aref totals first n))
        (setf (aref errors first n) (/ (aref errors first n) (aref totals first n))))
      (format output "~C~3D~%" #\tab (round (* 100 (aref errors first n)))))
    (format output "~%")
    (dotimes (second n)
      (unless (zerop (aref totals n second))
        (setf (aref errors n second) (/ (aref errors n second) (aref totals n second))))
      (format output "~3D~C" (round (* 100 (aref errors n second))) #\tab))
    (format output "~%Retrievals: ~D~%Computations: ~D~%" retrievals computations)
    (values retrievals computations)))

(defun analyze-latencies (problems n &key (cutoff n) (operator '+) (output t))
  (format output "~%Latencies:~%")
  (let ((zeroes (make-array (list (* 2 n) 2) :initial-element 0.0))
        (ties (make-array (list (* 2 n) 2) :initial-element 0.0))
        (by-sum (make-array (list (* 2 n) 2) :initial-element 0.0))
        (by-size (make-array (list 2 2) :initial-element 0.0)))
    (dolist (problem problems)
      (unless (and (zerop (problem-retrievals problem))
                   (zerop (problem-computations problem)))
        (let* ((first (problem-first problem))
               (second (problem-second problem))
               (sum (funcall operator first second))
               (size (if (>= sum cutoff) 1 0))
               (array (cond ((or (zerop first) (zerop second)) zeroes)
                            ((= first second) ties)
                            (t by-sum)))
               (latency (+ *io* (problem-latency problem))))
          (incf (aref array sum 0) latency)
          (incf (aref array sum 1) 1.0)
          (incf (aref by-size size 0) latency)
          (incf (aref by-size size 1) 1.0))))
    (dotimes (size 2)
      (when (> (aref by-size size 1) 0.0)
        (setf (aref by-size size 0)
              (/ (aref by-size size 0) (aref by-size size 1)))))
    (dotimes (sum (* 2 n))
      (dolist (array (list zeroes ties by-sum))
        (when (> (aref array sum 1) 0.0)
          (setf (aref array sum 0) (/ (aref array sum 0) (aref array sum 1)))
          (format output "~6,3F" (aref array sum 0)))
        (format output "~C" #\tab))
      (format output "~%"))
    (format output "~%Small: ~6,3F~%Large: ~6,3F~%"
            (aref by-size 0 0) (aref by-size 1 0))))

;;; Run a series of addition problems

(defun addition-problems (&key (difference 0.0005) (n 10) (max nil)
                               (delay 7500.0) (correct nil) (teach nil)
                               (runs 20) (set 2000) (feedback 1.0)
                               (reset t) (output t) (multiplication nil))
  (output-parameters output)
  (when reset
    (reset)
    (eval reset)
    (reset-facts)
    (set-count-unfocused-differential-history :repeat (/ set *count-set-ratio*)
                                              :delay (/ delay *count-delay-ratio*)))
  (no-output
   (let ((problems (generate-problems set n (even-frequency-table n difference)))
         (addition-feedback feedback)
         (multiplication-feedback feedback))
     (dotimes (run runs addition-feedback)
       (when (and (numberp multiplication) (>= run multiplication))
         (setf multiplication-feedback
               (multiplication-problems :difference difference :n n :max max
                                        :offset (- run multiplication)
                                        :delay delay :correct correct :teach teach
                                        :runs 1 :set set :feedback multiplication-feedback
                                        :output output)))
       (format output "~%ADDITION RUN ~D~%FEEDBACK PROBABILITY ~6,3F~%" (1+ run)
               addition-feedback)
       (let ((problems (copy-seq problems))
             (first nil)
             (second nil)
             (result nil)
             (retrieval-results nil)
             (computation-results nil))
         (dotimes (problem set)
           (setf *feedback* (< (random 1.0) addition-feedback))
           (multiple-value-setq (first second problems)
             (select-problem (- set problem)
                             problems))
           (setf result (addition-problem first second (or max (* 3 (+ second 3)))))
           (unless (zerop (problem-retrievals result))
             (push result retrieval-results))
           (unless (zerop (problem-computations result))
             (push result computation-results))
           (let ((cycles (correct-cycles teach)))
             (unless (equal (problem-answer result) (+ first second))
               (incf cycles (correct-cycles correct)))
             (when (> cycles 0)
               (rehearse-chunk-fct (list (list (get-addition-fact first second)
                                               (get-number first) '+
                                               (if (= first second)
                                                 'double (get-number second))))
                                   :force t :cycle cycles :time 1.0)))
           (when delay (actr-time-fct delay)))
         (setf addition-feedback
               (* feedback (- 1.0 (/ (analyze-errors retrieval-results n :output output) set))))
         (analyze-latencies retrieval-results n :output output)
         (analyze-errors computation-results n :output output)
         (analyze-latencies computation-results n :output output))))))

;;; Run a series of multiplication-by-repeated-addition problems

(defun repeated-addition-problem (i j &optional (max -1))
  (no-output
   (setf *repeated-answer* nil)
   (setf *addition-retrievals* 0)
   (setf *addition-computations* 0)
   (let ((goal (first (add-dm-fct
                       (list (list (gentemp "M") 'isa 'iterate-add
                                   'count 'n0 'limit (get-number i)
                                   'increment (get-number j)
                                   'tens 'n0 'units 'n0))
                       :reset-ia nil))))
     (goal-focus-fct (list goal))
     (let ((latency (run-fct max)))
       (clear-goal-stack)
       (setf *repeated-answer* (get-number-value *repeated-answer*))
       (make-problem :first i :operator '* :second j
                     :answer *repeated-answer* :latency latency
                     :retrievals *addition-retrievals*
                     :computations *addition-computations*)))))

(defun repeated-addition-problems (&key (difference 0.0) (n 10) (max nil)
                                        (delay 150.0) (samples 100) (output t))
  (no-output
   (let* ((problems (generate-problems samples n (even-frequency-table n difference)))
          (first nil)
          (second nil)
          (results nil))
     (format output "~%Multiplication as repeated addition: ")
     (dotimes (problem samples)
       (multiple-value-setq (first second problems)
         (select-problem (- samples problem) problems))
       (push (repeated-addition-problem first second (or max (* 15 (1+ first)))) results)
       (when delay (actr-time-fct delay)))
     (analyze-errors results n :operator '* :output output))))

;;; Multiplication

(defun get-multiplication-result (first second)
  (get-number (* (get-number-value first)
                 (get-number-value (if (eq second 'double) first second)))))
  
(defun multiplication-problem (i j &optional (max -1))
  (no-output
   (setf *multiplication-answer* nil)
   (setf *multiplication-retrievals* 0)
   (setf *multiplication-computations* 0)
   (let ((goal (first (add-dm-fct
                       (list (list (gentemp "A") 'isa 'arithmetic
                                   'first (get-number i)
                                   'operator '*
                                   'second (get-number j)))
                       :reset-ia nil))))
     (goal-focus-fct (list goal))
     (let ((latency (run-fct max)))
       (clear-goal-stack)
       (setf *multiplication-answer* (get-number-value *multiplication-answer*))
       (make-problem :first i :operator '* :second j
                     :answer *multiplication-answer* :latency latency
                     :retrievals *multiplication-retrievals*
                     :computations *multiplication-computations*)))))

(defun multiplication-problems (&key (difference 0.0005) (n 10) (max nil) (offset 0)
                                     (delay 7500.0) (correct nil) (teach nil)
                                     (runs 20) (set 2000) (feedback 1.0) (output t))
  (no-output
   (let ((problems (generate-problems set n (even-frequency-table n difference)))
         (multiplication-feedback feedback))
     (dotimes (run runs multiplication-feedback)
       (format output "~%MULTIPLICATION RUN ~D~%FEEDBACK PROBABILITY ~6,3F~%"
               (+ run 1 offset) multiplication-feedback)
       (let ((problems (copy-seq problems))
             (first nil)
             (second nil)
             (result nil)
             (retrieval-results nil)
             (computation-results nil))
         (dotimes (problem set)
           (setf *feedback* (< (random 1.0) multiplication-feedback))
           (multiple-value-setq (first second problems)
             (select-problem (- set problem) problems))
           (setf result (multiplication-problem first second (or max (* 15 (1+ first)))))
           (unless (zerop (problem-retrievals result))
             (push result retrieval-results))
           (unless (zerop (problem-computations result))
             (push result computation-results))
           (let ((cycles (correct-cycles teach)))
             (unless (equal (problem-answer result) (* first second))
               (incf cycles (correct-cycles correct)))
             (when (> cycles 0)
               (rehearse-chunk-fct (list (list (get-multiplication-fact first second)
                                               (get-number first) '*
                                               (if (= first second)
                                                 'double (get-number second))))
                                   :force t :cycle cycles :time 1.0)))
           (when delay (actr-time-fct delay)))
         (setf multiplication-feedback
               (* feedback (- 1.0 (/ (analyze-errors retrieval-results n :operator '*
                                                     :output output) set))))
         (analyze-latencies retrieval-results n :output output)
         (analyze-errors computation-results n :operator '* :output output)
         (analyze-latencies computation-results n :output output))))))

;;; Top level simulation function

(defun lifetime ()
  (addition-problems :multiplication 2))

;;; =====
;;; Model
;;; =====

(clear-all)

(sgp-fct (list :era t
               :bll 0.5
               :al 1.0
               :ans *ans*
               :rt *rt*
               :lf *lf*
               :pm t
               :mp *mp*
               :v nil))

(chunk-type operator function)

(chunk-type number tens units value)

(chunk-type count from to)

(chunk-type arithmetic first operator second result)

(chunk-type iterate-count count limit result)

(chunk-type iterate-add count limit increment tens units)

(chunk-type split number tens units)

(add-dm
 ;;; symbols
 (+ isa operator function #'+)
 (double isa operator function #'+)
 (* isa operator function #'*)
 ;;; numbers
 (n0 isa number tens n0 units n0 value 0)
 (n1 isa number tens n0 units n1 value 1)
 (n2 isa number tens n0 units n2 value 2)
 (n3 isa number tens n0 units n3 value 3)
 (n4 isa number tens n0 units n4 value 4)
 (n5 isa number tens n0 units n5 value 5)
 (n6 isa number tens n0 units n6 value 6)
 (n7 isa number tens n0 units n7 value 7)
 (n8 isa number tens n0 units n8 value 8)
 (n9 isa number tens n0 units n9 value 9)
 (n10 isa number tens n1 units n0 value 10)
 (n11 isa number tens n1 units n1 value 11)
 (n12 isa number tens n1 units n2 value 12)
 (n13 isa number tens n1 units n3 value 13)
 (n14 isa number tens n1 units n4 value 14)
 (n15 isa number tens n1 units n5 value 15)
 (n16 isa number tens n1 units n6 value 16)
 (n17 isa number tens n1 units n7 value 17)
 (n18 isa number tens n1 units n8 value 18)
 (n19 isa number tens n1 units n9 value 19)
 )

;; prevent mismatches between + and *
(set-similarities (+ * -10.0))

(set-number-similarities)

;;; ===================
;;; counting production
;;; ===================

(p count-up
   =goal>
      isa count
      from =from
      to nil
   =fact>
      isa count
      from =from
      to =to
==>
   !output! ("Counting from ~S to ~S" =from =to)
   !eval! (progn (setf *count-answer* =to) (incf *count-retrievals*))
   =goal>
      to =to
   !pop!)

;;; feedback production

(p solve-count
   =goal>
      isa count
      from =from
      to nil
   !eval! *count-feedback*
==>
   !bind! =to (get-count-result =from)
   !eval! (setf *count-answer* =to)
   =goal>
      to =to
   !pop!)

;;; ======================
;;; arithmetic productions
;;; ======================

(p done-arithmetic
   =goal>
      isa arithmetic
      first =first
      operator =operator
      second =second
      result =result
==>
   !output! ("Result ~S ~S ~S = ~S" =first =operator =second =result)
   !eval! (if (equal =operator '+)
            (setf *addition-answer* =result)
            (setf *multiplication-answer* =result))
   !pop!)

(p first-plus-zero
   =goal>
      isa arithmetic
      first =first
      operator +
      second n0
      result nil
==>
   !output! ("~S + 0 = ~S" =first =first)
   =goal>
      result =first)   

(p zero-plus-second
   =goal>
      isa arithmetic
      first n0
      operator +
      second =second
      result nil
==>
   !output! ("0 + ~S = ~S" =second =second)
   =goal>
      result =second)

(spp (first-plus-zero zero-plus-second) :effort 0.7)

(p double-recoding
   =goal>
      isa arithmetic
      first =first
      operator =operator
      second =first
      result nil
==>
   !output! ("Recoding ~S ~S ~S as ~S ~S Double"
             =first =operator =first =first =operator)
   =subgoal>
      isa arithmetic
      first =first
      operator =operator
      second double
      result =result
   !push! =subgoal
   =goal>
      result =result)

(p arithmetic-retrieval
   =goal>
      isa arithmetic
      first =first
      operator =operator
      second =second
      result nil
   =fact>
      isa arithmetic
      first =first
      operator =operator
      second =second
      result =result
==>
   !output! ("Retrieving ~S" =fact)
   !eval! (if (equal =operator '+)
            (incf *addition-retrievals*)
            (incf *multiplication-retrievals*))
   =goal>
      result =result)   

;;; ====================
;;; Addition Computation
;;; ====================

;;; feedback production

(p solve-addition
   =goal>
      isa arithmetic
      first =first
      operator +
      second =second
      result nil
   !eval! *feedback*
==>
   !bind! =result (get-addition-result =first =second)
   =goal>
      result =result)

(p double-counting
   =goal>
      isa arithmetic
      first =first
      operator +
      second double
      result nil
==>
   =subgoal>
      isa iterate-count
      count n0
      limit =first
      result =first
      result =result
   !push! =subgoal
   !output! ("Subgoaling ~S" =subgoal)
   !eval! (incf *addition-computations*)
   =goal>
      result =result)

(p addition-counting
   =goal>
      isa arithmetic
      first =first
      operator +
      second =second
      result nil
==>
   =subgoal>
      isa iterate-count
      count n0
      limit =second
      result =first
      result =result
   !push! =subgoal
   !output! ("Subgoaling ~S" =subgoal)
   !eval! (incf *addition-computations*)
   =goal>
      result =result)

;;; iterate-count productions

(p done-count
   =goal>
      isa iterate-count
      count =count
      limit =count
      result =result
==>
   !output! ("Done with count ~S and result ~S" =count =result)
   !pop!)

(p iterate-count
   =goal>
      isa iterate-count
      count =count
    - limit =count
      result =result
    - result failure
==>
   !output! ("Incrementing ~S and ~S" =count =result)
   =subgoal1>
      isa count
      from =count
      to =next-count
   =subgoal2>
      isa count
      from =result
      to =next-result
   !push! =subgoal2
   !push! =subgoal1
   =goal>
      count =next-count
      result =next-result)

(spp iterate-count :effort 0.5)

;;; ==========================
;;; Multiplication Computation
;;; ==========================

;;; feedback production

(p solve-multiplication
   =goal>
      isa arithmetic
      first =first
      operator *
      second =second
      result nil
   !eval! *feedback*
==>
   !bind! =result (get-multiplication-result =first =second)
   =goal>
      result =result)

(p double-adding
   =goal>
      isa arithmetic
      first =first
      operator *
      second double
      result nil
==>
   =subgoal>
      isa iterate-add
      count n0
      limit =first
      increment =first
      tens n0
      units n0
      units =result
   !push! =subgoal
   !output! ("Subgoaling ~S" =subgoal)
   !eval! (incf *multiplication-computations*)
   =goal>
      result =result)

(p multiplication-adding
   =goal>
      isa arithmetic
      first =first
      operator *
      second =second
      result nil
==>
   =subgoal>
      isa iterate-add
      count n0
      limit =first
      increment =second
      tens n0
      units n0
      units =result
   !push! =subgoal
   !output! ("Subgoaling ~S" =subgoal)
   !eval! (incf *multiplication-computations*)
   =goal>
      result =result)

;;; iterate-add productions

(p construct-result
   =goal>
      isa iterate-add
      count =count
      limit =count
      tens =tens
    - tens failure
      units =units
    - units failure
==>
   !output! ("Constructing the result from tens ~S and units ~S"
             =tens =units)
   !bind! =value (+ (* 10 (chunk-slot-value-fct =tens 'value))
                    (chunk-slot-value-fct =units 'value))
   =number>
      isa number
      tens =tens
      units =units
      value =value
   !push! =number
   =goal>
      tens nil
      units =number)

(p done-add
   =goal>
      isa iterate-add
      count =count
      limit =count
      tens nil
      units =answer
==>
   !output! ("The answer is ~S" =answer)
   !eval! (setf *repeated-answer* =answer)
   !pop!)

(p iterate-add
   =goal>
      isa iterate-add
      count =count
    - limit =count
      increment =increment
      tens =tens
      units =units
==>
   !output! ("Incrementing ~S and adding ~S to tens ~S and units ~S"
             =count =increment =tens =units)
   =count-subgoal>
      isa arithmetic
      first =count
      operator +
      second n1
      result =next-count
   =add-subgoal>
      isa arithmetic
      first =units
      operator +
      second =increment
      result =answer
   =split-goal>
      isa split
      number =answer
      tens =carry
      units =new-units
   =carry-goal>
      isa arithmetic
      first =tens
      operator +
      second =carry
      result =new-tens
  !push! =carry-goal
  !push! =split-goal
  !push! =add-subgoal
  !push! =count-subgoal
   =goal>
      count =next-count
      tens =new-tens
      units =new-units)

;;; Split production

(p split
   =goal>
      isa split
      number =number
      tens nil
      units nil
==>
   !bind! =tens (chunk-slot-value-fct =number 'tens)
   !bind! =units (chunk-slot-value-fct =number 'units)
   !output! ("Splitting ~S into tens ~S and units ~S" =number =tens =units)
   =goal>
      tens =tens
      units =units
   !pop!)

;;; Number creation upon merging

(p merge-numbers
   =goal>
      isa number
      tens =tens
      units =units
      value =value
==>
   !output! ("Number ~S has value ~S" =goal =value)
   !pop!)