;;; -*- mode: LISP; Package: CL-USER; Syntax: COMMON-LISP; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Author : Mike Byrne & Dan Bothell ;;; Copyright : (c)1997-2002 CMU/Rice U./Mike Byrne, All Rights Reserved ;;; Availability: public domain ;;; Address : Rice University ;;; : Psychology Department ;;; : Houston,TX 77251-1892 ;;; : byrne@acm.org ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Filename : * load-rpm.lisp ;;; Version : 2.1b4 ;;; ;;; Description : File loader for ACT-R/PM system. ;;; ;;; 99.12.21 Dan Bothell ;;; : Made the loader ACL friendly - it now assumes that the ;;; : files are located in the same dir as the load file ;;; : Added a feature check to quickdraw. ;;; : The *file-list depends on which system is running (there should ;;; : probably be a neutral list also, but I didn't add it). ;;; 01.04.12 Dan Bothell ;;; : Changed it to load the ACT-R 5.0 files. ;;; : Rearranged the order slightly to put the ;;; : actr-interface after device-interface ;;; : so that process-display could be redefined. ;;; : Added an ACL specific smart-load that checks ;;; : for zero length fasl's to allow updates ;;; : for the Windows environment to work safely. ;;; 01.05.31 Dan Bothell ;;; : Added a default *file-list definition and made the allegro ;;; : definition version specific (in an effort to make R/PM more general). ;;; : Added a LispWorks smart-loader because it didn't like the ;;; : pathname handling of the old one. ;;; : Also removed act-sources from the lists because with 5.0 it's ;;; : not an issue anymore. ;;; 01.06.15 Dan ;;; : Added a loader for clisp (man is it super fast ;;; : it's an order of magnitude faster than MCL or ACL). ;;; 01.09.17 mdb ;;; : Made some file changes for 2.1b1 ;;; ;;; 02.01.15 Dan ;;; : Changed the file lists to include the right UWI ;;; : files, changed the allegro feature tests so that ;;; : it works better for ACL versions other than 5.0.1, ;;; : and added a warning (at the end) for ACL users that ;;; : tells them not to use the Modern mode (case sensitive) ;;; : version of ACL. ;;; 02.02.11 Dan ;;; : added a new file "uniform-interface-exp.lisp" for the UWI experiment interface ;;; : basically an "easier" interface to the UWI for use by tutorial models ;;; : mostly just hiding of the need to keep a pointer to the window ;;; : around to do things. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; #+:mcl (require 'quickdraw) (unless (boundp '*.lisp-pathname*) (defvar *.lisp-pathname* (make-pathname :type "lisp"))) (unless (boundp '*.fasl-pathname*) (defvar *.fasl-pathname* (make-pathname :type "fasl"))) ;;; Define the files to be loaded. ;;; start with a generic list (defparameter *file-list '("actr5" "actr5-pm-compiler-fix" "rpm-utils" "dmi" "rpm-parameters" "master-process" "pm-module" "vision-categorization" "vision-module" "motor-module" "speech-module" "audio-module" "device-interface" "actr-interface" "cognition-module" "virtual-view" "generic-interface" "uniform-interface-virtual" "uniform-interface-exp" "view-line-virtual" "patch-support" "rpm-toplevel" "initialize-rpm" )) ;;; then switch it to specifics if possible #+:mcl (defparameter *file-list '("actr5" "actr5-pm-compiler-fix" "rpm-utils" "dmi" "rpm-parameters" "master-process" "pm-module" "vision-categorization" "vision-module" "motor-module" "speech-utils" "speech-module" "audio-module" "device-interface" "actr-interface" "cognition-module" "virtual-view" "mcl-interface" "uniform-interface-virtual" "view-line-virtual" "uniform-interface-mcl" "uniform-interface-exp" "view-line-mcl" "patch-support" "rpm-toplevel" "initialize-rpm" )) #+(and :ALLEGRO-IDE :allegro-version>= (version>= 5)) (defparameter *file-list '("actr5" "actr5-pm-compiler-fix" "rpm-utils" "dmi" "rpm-parameters" "master-process" "pm-module" "vision-categorization" "vision-module" "motor-module" "speech-module" "audio-module" "device-interface" "actr-interface" "cognition-module" "virtual-view" "acl-interface" "uniform-interface-virtual" "uniform-interface-acl" "uniform-interface-exp" "view-line-virtual" "view-line-acl" "patch-support" "rpm-toplevel" "initialize-rpm" )) ;;; SMART-LOAD [Function] ;;; Date : 99.12.21 ;;; Description : Loads binary version of a specified file. Of course, the said ;;; : binary version might not exist or be older than the source ;;; : version, in which case the source file is compiled before ;;; : loading. Note that this will almost surely break under non-MCL ;;; : Lisps. (defun smart-load (this-files-dir file) "Loads binary in directory or compiles and loads source version" (let* ((the-dir (directory-namestring this-files-dir)) (srcpath (merge-pathnames (make-pathname :directory the-dir :name file) *.lisp-pathname*)) (binpath (merge-pathnames (make-pathname :directory the-dir :name file) *.fasl-pathname*))) (unless (probe-file srcpath) (error "File ~S does not exist" srcpath)) (when (or (not (probe-file binpath)) (> (file-write-date srcpath) (file-write-date binpath))) (compile-file srcpath)) (load binpath))) #+:clisp (defun smart-load (this-files-dir file) "Loads binary in directory or compiles and loads source version" (let* ((srcpath (merge-pathnames (make-pathname :name file :type "lisp") this-files-dir)) (binpath (merge-pathnames (make-pathname :name file :type "fas") this-files-dir))) (unless (probe-file srcpath) (error "File ~S does not exist" srcpath)) (when (or (not (probe-file binpath)) (> (file-write-date srcpath) (file-write-date binpath))) (compile-file srcpath)) (load binpath))) #+(and :linux :cmu) (defun smart-load (this-files-dir file) "Loads binary in directory or compiles and loads source version" (let* ((srcpath (merge-pathnames (make-pathname :name file :type "lisp") this-files-dir)) (binpath (merge-pathnames (make-pathname :name file :type "x86f") this-files-dir))) (unless (probe-file srcpath) (error "File ~S does not exist" srcpath)) (when (or (not (probe-file binpath)) (> (file-write-date srcpath) (file-write-date binpath))) (compile-file srcpath)) (load binpath))) #+:lispworks (defun smart-load (this-files-dir file) "Loads binary in directory or compiles and loads source version" (let* ((srcpath (merge-pathnames (make-pathname :name file :type "lisp") this-files-dir)) (binpath (merge-pathnames (make-pathname :name file :type "fsl") this-files-dir))) (unless (probe-file srcpath) (error "File ~S does not exist" srcpath)) (when (or (not (probe-file binpath)) (> (file-write-date srcpath) (file-write-date binpath))) (compile-file srcpath :output-file binpath)) (load binpath))) #+(and :ALLEGRO-IDE :allegro-version>= (version>= 5)) (defun smart-load (this-files-dir file) "Loads binary in directory or compiles and loads source version with a test on the fasl length to make patches for windows environment safe" (let* ((the-dir (directory-namestring this-files-dir)) (srcpath (merge-pathnames (make-pathname :directory the-dir :name file) *.lisp-pathname*)) (binpath (merge-pathnames (make-pathname :directory the-dir :name file) *.fasl-pathname*))) (unless (probe-file srcpath) (error "File ~S does not exist" srcpath)) (when (or (not (probe-file binpath)) (zerop (file-size binpath)) (> (file-write-date srcpath) (file-write-date binpath))) (compile-file srcpath)) (load binpath))) (defun load-actrpm (this-file) (dolist (the-file *file-list) (smart-load this-file the-file))) ;;; If the user isn't using ACL then load things as ;;; normal. #-:allegro (let ((*record-source-file* nil)) (load-actrpm *load-pathname*)) ;;; Otherwise first check to see if they are in the ;;; ACL Modern mode, and if so warn them that they ;;; should be using the ANSI version. #+:allegro (if (or (and (not (eq :case-sensitive-lower *current-case-mode*)) (not (eq :case-sensitive-upper *current-case-mode*))) (yes-or-no-p "WARNING: you are using a case sensitive Lisp. ACT-R will not load and run correctly. Continue anyway?")) (let ((*record-source-file* nil)) (load-actrpm *load-pathname*)) (format t "ACT-R 5 was NOT loaded.")) ;;; -*- mode: LISP; Package: CL-USER; Syntax: COMMON-LISP; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Author : Dan Bothell (plus code from Tech support at Franz Inc.) ;;; Copyright : (c)2000 CMU/Dan Bothell, All Rights Reserved ;;; Availability: public domain ;;; Address : Carnegie Mellon University ;;; : Psychology Department ;;; : Pittsburgh,PA 15213-3890 ;;; : db30+@andrew.cmu.edu ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Filename : acl-interface.lisp ;;; Version : 2.0b3 ;;; ;;; Description : ACL-specific functions for RPM. This consists primarily ;;; : of stuff for vision (parsing the screen), and output ;;; : stuff for motor. ;;; ;;; Bugs : ;;; ;;; --- History --- ;;; 00.01.25 Dan Bothell ;;; : First version. ;;; : function comments copied from mcl-interface. ;;; 00.06.08 mdb ;;; : Moved POPULATE-LOC-TO-KEY-ARRAY method here. ;;; 00.09.05 Dan ;;; : Fixed all of the text feature building to correctly ;;; : use ascent and descent parameters for the font instead of ;;; : only using the font-height. ;;; : Made the changes to bring it up to speed with 2.0b3 ;;; : Specifically: ;;; : Added a focus ring. ;;; : Changed from xy lists to vectors. ;;; : Fixed the build-features-for for buttons. ;;; : Added the approach-width method for text features ;;; : so that if it's on a button it's computed correctly. ;;; 01.04.12 Dan ;;; : Added a hack to get around the keypad not returning ;;; : numbers. ;;; : Changed the focus ring so that it didn't 'eat' the ;;; : the keypresses. ;;; 02.01.15 Dan ;;; : Added some feature checks to better handle ACL versions ;;; : other than 5.0.1. It's still not perfect however because ;;; : in ACL 6 or newer the focus ring doesn't erase ;;; : and I haven't figured out how to fix that yet. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass rpm-window (dialog) ()) (defvar *simulated-key* nil) (defmethod window-select ((w window)) (select-window w)) (defmethod device-move-cursor-to ((device window) (xyloc vector)) (setf (cursor-position device) (make-position (px xyloc) (py xyloc)))) (defmethod device-speak-string ((device window) string) (declare (ignore string))) (defmethod get-mouse-coordinates ((device window)) (let ((cur-pos (cursor-position device))) (vector (position-x cur-pos) (position-y cur-pos)))) (defmethod device-handle-click ((device window)) (rpm-window-click-event-handler device (list (position-x (cursor-position device)) (position-y (cursor-position device)))) (do-click nil :preview-seconds nil :down-seconds .0001)) (defmethod mouse-left-down ((device rpm-window) buttons cur-pos) (declare (ignore buttons)) (unless *actr-enabled-p* (when (null cur-pos) (setf cur-pos (cursor-position device))) (rpm-window-click-event-handler device (list (position-x cur-pos) (position-y cur-pos))))) (defmethod rpm-window-click-event-handler ((device window) position) (declare (ignore device position))) (defmethod device-handle-keypress ((device window) key) (do-keypress device key :preview-seconds nil :down-seconds nil) (unless (subtypep (type-of device) 'rpm-window) (rpm-window-key-event-handler device key))) (defmethod rpm-window-key-event-handler ((device window) key) (declare (ignore device position))) (defmethod do-update :after ((mstr-proc master-process) current-time &key (real-wait nil)) (declare (ignore current-time real-wait)) (process-pending-events)) (defmethod virtual-key-down :before ((device rpm-window) buts key) (declare (ignore-if-unused device buts key)) (unless (equal key 16) ;;; if it's the shift key could be problems ;;; this is a big hack to get the num-pad 1-9 to return correctly ;;; I can't come up with a better way right now, so this is ;;; how it's got to be (rpm-window-key-event-handler device (case key (97 #\1) (98 #\2) (99 #\3) (100 #\4) (101 #\5) (102 #\6) (103 #\7) (104 #\8) (105 #\9) (t (code-char key))))) t) (defun loc-avg (x y) (declare (fixnum x) (fixnum y)) (floor (/ (+ x y) 2))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; ---------------------------------------------------------------------- ;;;;;;; ;;;; ACL screen-to-icon interface ;;;; ---------------------------------------------------------------------- ;;;;;;; ;;; BUILD-FEATURES-FOR [Method] ;;; Description : For an ACL window, just walk the sub-objects (which by ;;; : default are subviews) and build features. (defmethod build-features-for ((self window) (vis-mod vision-module)) (let ((base-ls (flatten (mapcar #'(lambda (obj) (build-features-for obj vis-mod)) (get-sub-objects self))))) (dolist (feat base-ls) (fill-default-dimensions feat)) base-ls)) (defmethod get-sub-objects ((v window)) "Grabbing the sub-objects of a window by default returns the dialog-items." (dialog-items v)) ;;; BUILD-FEATURES-FOR [Method] ;;; Date : 97.01.27 ;;; Description : The basic method for building features, returns an object ;;; : of the ICON-FEATURE class with the location and ISA fields ;;; : set. (defmethod build-features-for ((self dialog-item) (vis-mgr vision-module)) "Build an icon feature for the dialog item" (make-instance 'icon-feature :x (px (view-loc self)) :y (py (view-loc self)) :isa 'visual-object :value 'unknown :screen-obj self)) ;;; BUILD-FEATURES-FOR [Method] ;;; Date : 97.01.28 ;;; Description : Same as for EDIT-BOXes. (defmethod build-features-for ((self editable-text) (vis-mod vision-module)) "Builds an icon feature for an EDITABLE-TEXT-DIALOG-ITEM" (let* ((font-spec (nfontmetrics (window self) (make-fontmetrics))) (ascent (font-ascent font-spec)) (descent (font-descent font-spec)) (text (value self))) (cons (make-instance 'rect-feature :x (px (view-loc self)) :y (py (view-loc self)) :screen-obj self) (unless (equal text "") (build-string-feats vis-mod :text text :start-x (1+ (box-left (box self))) :y-pos (+ (box-top (box self)) descent (round ascent 2)) :width-fct #'(lambda (str) (string-width str self)) :height ascent :obj self))))) ;;; BUILD-FEATURES-FOR [Method] ;;; Date : 99.04.02, delta 99.07.02 ;;; Description : A button dialog item is a lot like a static text item, ;;; : except there's an oval associated with it and the text ;;; : is centered both horizontally and vertically. (defmethod build-features-for ((self button) (vis-mod vision-module)) "Builds an icon feature for a BUTTON DIALOG-ITEM" (let* ((btn-width (width self)) (btn-height (height self)) (text (title self))) (cons (make-instance 'oval-feature :x (px (view-loc self)) :y (py (view-loc self)) :width btn-width :height btn-height :screen-obj self) (unless (equal text "") (let* ((font-spec (nfontmetrics (window self) (make-fontmetrics))) (ascent (font-ascent font-spec)) (descent (font-descent font-spec)) (textlines (string-to-lines text)) (start-y (+ (box-top (box self)) (round (- btn-height (* (length textlines) (+ ascent descent))) 2))) (accum nil) (width-fct #'(lambda (str) (string-width str self)))) (dolist (item textlines (nreverse accum)) (push (build-string-feats vis-mod :text text :start-x (+ (box-left (box self)) (round (- btn-width (funcall width-fct text)) 2)) :y-pos (+ start-y (round (+ ascent descent) 2)) :width-fct width-fct :height (min ascent btn-height) :obj self) accum) (incf start-y (+ ascent descent)))))))) ;;; BUILD-FEATURES-FOR [Method] ;;; Date : 99.04.02 ;;; Description : A static text dialog item is really just text, so just ;;; : build the string features for it. (defmethod build-features-for ((self static-text) (vis-mod vision-module)) (let* ((font-spec (nfontmetrics (window self) (make-fontmetrics))) (ascent (font-ascent font-spec)) (descent (font-descent font-spec)) (text (value self))) (unless (equal text "") (build-string-feats vis-mod :text text :start-x (1+ (box-left (box self))) :y-pos (+ (box-top (box self)) descent (round ascent 2)) :width-fct #'(lambda (str) (string-width str self)) :height ascent :obj self)))) (defun string-width (str item) (stream-string-width (window item) str)) (defmethod approach-width ((feat text-feature) (theta number)) (let ((screen-obj (screen-obj feat))) (if (and screen-obj (eq 'button (class-name (class-of screen-obj)))) (let ((new-feat (first (member screen-obj (visicon (vis-m *mp*)) :key #'screen-obj)))) (if new-feat (approach-width new-feat theta) (call-next-method))) (call-next-method)))) ;;; CURSOR-TO-FEATURE [Function] ;;; Date : 97.06.16 ;;; Description : Returns a feature representing the current state and shape ;;; : of the cursor. (defmethod cursor-to-feature ((the-window window)) "Returns a feature corresponding to the current cursor." (let ((pos (cursor-position the-window)) (shape (cursor the-window))) (when (cursor-in-window-p the-window) (make-instance 'cursor-feature :x (position-x pos) :y (position-y pos) :value (case (name shape) (:line-cursor 'I-BEAM) (:cross-cursor 'CROSSHAIR) (:waiting-cursor 'WATCH) (otherwise 'POINTER)))))) (defmethod cursor-in-window-p (tw) "Returns T if the cursor is over the input window, NIL otherwise." (or (equal (window-under-mouse) tw) (equal (parent (window-under-mouse)) tw))) (defmethod view-loc ((self dialog-item)) "Return the center point of a view in (X Y) format." (let ((b (box self)) ) (vector (+ (box-left b) (round (/ (- (box-right b) (box-left b)) 2))) (+ (box-top b) (round (/ (- (box-bottom b) (box-top b)) 2)))))) (defmethod view-loc ((self symbol)) "Hacked VIEW-LOC method for the cursor--returns the cursor location as (X Y)." (if (eq self :cursor) (get-mouse-coordinates (device (device-interface *mp*))) (error "!! Can't find location of ~S" self))) ;;; FILL-DEFAULT-DIMENSIONS [Method] ;;; Date : 99.04.02 ;;; Description : The base methods for most MCL views don't set the height or ;;; : width attributes of the features they generate, nor their ;;; : size. Set that up if necessary. (defmethod fill-default-dimensions ((feat icon-feature)) "Fill in the width, height, and size of an icon feature." (aif (simple-size feat) (setf (size feat) it) (if (null (screen-obj feat)) (setf (size feat) 1.0) ; should be default size, eh (progn (unless (width feat) (setf (width feat) (width (screen-obj feat)))) (unless (height feat) (setf (height feat) (height (screen-obj feat)))) (setf (size feat) (simple-size feat)))))) (defmethod populate-loc-to-key-array ((ar array)) "Sets all the keys in the array that need to be set" ;; function key row (setf (aref ar 0 0) #\ESC) (setf (aref ar 2 0) vk-f1) (setf (aref ar 3 0) vk-f2) (setf (aref ar 4 0) vk-f3) (setf (aref ar 5 0) vk-f4) (setf (aref ar 7 0) vk-f5) (setf (aref ar 8 0) vk-f6) (setf (aref ar 9 0) vk-f7) (setf (aref ar 10 0) vk-f8) (setf (aref ar 12 0) vk-f9) (setf (aref ar 13 0) vk-f10) (setf (aref ar 14 0) vk-f11) (setf (aref ar 15 0) vk-f12) (setf (aref ar 17 0) vk-f13) (setf (aref ar 18 0) vk-f14) (setf (aref ar 19 0) vk-f15) ;; numeric key row (setf (aref ar 0 2) vk-backquote) (setf (aref ar 1 2) #\1) (setf (aref ar 2 2) #\2) (setf (aref ar 3 2) #\3) (setf (aref ar 4 2) #\4) (setf (aref ar 5 2) #\5) (setf (aref ar 6 2) #\6) (setf (aref ar 7 2) #\7) (setf (aref ar 8 2) #\8) (setf (aref ar 9 2) #\9) (setf (aref ar 10 2) #\0) (setf (aref ar 11 2) #\-) (setf (aref ar 12 2) #\=) (setf (aref ar 13 2) vk-backspace) (setf (aref ar 15 2) vk-insert) (setf (aref ar 16 2) vk-home) (setf (aref ar 17 2) vk-pageup) (setf (aref ar 19 2) vk-numlock) (setf (aref ar 20 2) #\=) (setf (aref ar 21 2) #\/) (setf (aref ar 22 2) #\*) ;; qwerty row (setf (aref ar 0 3) #\Tab) (setf (aref ar 1 3) #\Q) (setf (aref ar 2 3) #\W) (setf (aref ar 3 3) #\E) (setf (aref ar 4 3) #\R) (setf (aref ar 5 3) #\T) (setf (aref ar 6 3) #\Y) (setf (aref ar 7 3) #\U) (setf (aref ar 8 3) #\I) (setf (aref ar 9 3) #\O) (setf (aref ar 10 3) #\P) (setf (aref ar 11 3) #\[) (setf (aref ar 12 3) #\]) (setf (aref ar 13 3) #\\) (setf (aref ar 15 3) vk-delete) (setf (aref ar 16 3) vk-end) (setf (aref ar 17 3) vk-pagedown) (setf (aref ar 19 3) #\7) (setf (aref ar 20 3) #\8) (setf (aref ar 21 3) #\9) (setf (aref ar 22 3) #\-) ;; ASDF row (setf (aref ar 0 4) vk-capslock) (setf (aref ar 1 4) #\A) (setf (aref ar 2 4) #\S) (setf (aref ar 3 4) #\D) (setf (aref ar 4 4) #\F) (setf (aref ar 5 4) #\G) (setf (aref ar 6 4) #\H) (setf (aref ar 7 4) #\J) (setf (aref ar 8 4) #\K) (setf (aref ar 9 4) #\L) (setf (aref ar 10 4) #\;) (setf (aref ar 11 4) vk-quote) (setf (aref ar 12 4) #\return) (setf (aref ar 13 4) #\return) (setf (aref ar 19 4) #\4) (setf (aref ar 20 4) #\5) (setf (aref ar 21 4) #\6) (setf (aref ar 22 4) #\+) ;; Z row (setf (aref ar 0 5) vk-shift) (setf (aref ar 1 5) #\Z) (setf (aref ar 2 5) #\X) (setf (aref ar 3 5) #\C) (setf (aref ar 4 5) #\V) (setf (aref ar 5 5) #\B) (setf (aref ar 6 5) #\N) (setf (aref ar 7 5) #\M) (setf (aref ar 8 5) #\,) (setf (aref ar 9 5) #\.) (setf (aref ar 10 5) #\/) (setf (aref ar 11 5) vk-shift) (setf (aref ar 12 5) vk-shift) (setf (aref ar 16 5) vk-up) (setf (aref ar 19 5) #\1) (setf (aref ar 20 5) #\2) (setf (aref ar 21 5) #\3) (setf (aref ar 22 5) vk-enter) ;; space bar row (setf (aref ar 0 6) vk-control) (setf (aref ar 1 6) 'option) (setf (aref ar 2 6) vk-alt) (setf (aref ar 3 6) #\Space) (setf (aref ar 4 6) #\Space) (setf (aref ar 5 6) #\Space) (setf (aref ar 6 6) #\Space) (setf (aref ar 7 6) #\Space) (setf (aref ar 8 6) #\Space) (setf (aref ar 9 6) #\Space) (setf (aref ar 10 6) #\Space) (setf (aref ar 11 6) vk-alt) (setf (aref ar 12 6) 'option) (setf (aref ar 13 6) vk-control) (setf (aref ar 15 6) vk-left) (setf (aref ar 16 6) vk-down) (setf (aref ar 17 6) vk-right) (setf (aref ar 19 6) #\0) (setf (aref ar 20 6) #\0) (setf (aref ar 21 6) #\.) (setf (aref ar 22 6) vk-enter) ar) ;;; DEVICE-UPDATE-ATTENDED-LOC [Method] ;;; Date : 00.09.05 ;;; Description : When the attended location is updated, update the focus ;;; : ring. Create a new one and add it to the window if the ;;; : previous one was closed, exists in a different window, or ;;; : if it hasn't been created yet. ;;; : Differs from MCL's in that it doesn't exist outside of a ;;; : device window. (defmethod device-update-attended-loc ((wind window) xyloc) (unless (or (equal t *attn-tracker*) #+:allegro-V5.0.1 (equal (type-of *attn-tracker*) 'closed-stream) #+(version>= 6) (null (handle win)) (equal wind (parent *attn-tracker*))) (close *attn-tracker*)) (when (or (eql t *attn-tracker*) #+:allegro-V5.0.1 (equal (type-of *attn-tracker*) 'closed-stream) #+(version>= 6) (null (handle win)) ) (setf *attn-tracker* (make-window :focus-ring :device 'focal-view :parent wind :left (- (px xyloc) 11) :right (- (py xyloc) 11)))) (update-me *attn-tracker* wind xyloc)) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Focus ring stuff (defclass focal-view (transparent-pane) () (:default-initargs :foreground-color red :width 23 :height 23)) ;;; REDISPLAY-WINDOW [Method] ;;; Date : 00.09.05 ;;; Description : Draws a red ring centered in the center of the transparent ;;; : pane, which should be at the focus of attention. (defmethod redisplay-window ((self focal-view) &optional box) (declare (ignore box)) (setf (line-width self) 3) (draw-circle self (make-position 11 11) 10)) ;;; UPDATE-ME [Method] ;;; Date : 00.09.05 ;;; Description : Udating the focus ring means changing its location. ;;; : It's hidden before moving to prevent disruption of ;;; : other screen objects (problem with ACL transparent pane). (defmethod update-me ((foc-ring focal-view) window xyloc) (setf (state foc-ring) :shrunk) (setf (left foc-ring) (- (px xyloc) 11)) (setf (top foc-ring) (- (py xyloc) 11)) (setf (state foc-ring) :normal)) (eval-when (load eval) (setf *attn-tracker* t)) ;;; hack so that when the focus-ring is present it passes the key presses on to the ;;; window for handling - oh yeah it actually works (defmethod virtual-key-down :before ((focus-ring focal-view) buttons key-code) (when (subtypep (type-of (parent focus-ring)) 'rpm-window) (virtual-key-down (parent focus-ring) buttons key-code))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; The following code was given to me by a developer at Franz to simulate the ;;; mouse clicking and the button pressing in a window so that all of the ;;; correct ACL 'actions' occur (methods get called, window is selected, visual ;;; action occurs, etc). #| Our developer most familiar with Common Graphics has found a way to accomplish what you were trying to do. He has written some code (attached below my signature) which when loaded into your lisp will allow you to run some examples showing a number of ways to simulate keypresses and button clicks. Using the following definition of a window (which contains 2 widgets) (defun make-form1 (&key (parent (development-main-window *system*)) (exterior (make-box 256 167 944 534)) (name :form1) (title "Form1") form-p) (let ((parent (make-window name :parent parent :device 'dialog :exterior exterior :border :frame :close-button t :cursor-name :arrow-cursor :widgets (list (setf w3 (make-instance 'button :font (make-font-ex nil "MS Sans Serif" 13 nil) :left 176 :on-change 'foo :name :button4 :top 24)) (setf w4 (make-instance 'editable-text :font (make-font-ex nil "MS Sans Serif" 13 nil) :left 106 :name :editable-text-1 :template-string nil :top 208 :up-down-control nil :value "EDITABLE-TEXT"))) :form-state :normal :maximize-button t :minimize-button t :name :form1 :package-name :common-graphics-user :pop-up nil :resizable t :scrollbars nil :state :normal :status-bar nil :system-menu t :title title :title-bar t :toolbar nil :form-p form-p :path #p"C:\\Program Files\\acl50195pf\\form1.bil" :help-string nil :package-name :common-graphics-user))) parent)) (defun foo(widget new-value old-value) (princ "hello")) You can try running the following examples using the widgets w3 and w4. For example: (do-click w3) will call the button to be pressed and the on-change function foo to run. Foo simply prints "hello" to the debug window. Examples (where "it" is either a window or widget): ;; Mouse clicks ;; Left-click in the center of its scrollable page. (do-click it) ;; Left-click near the upper-right corner of its scrollable page. (do-click it :position (make-position (- (interior-width (window it)) 8) 12)) ;; Left-click it with no pause for people to watch the action. (do-click it :preview-seconds nil :down-seconds nil) ;; Right-click in the center of its client area. (do-click it :button :right) ;; Left-click wherever the mouse is now. (do-click nil) ;; Left-click an arbitary position on the screen. (do-click nil :position (make-position 100 200)) ;; Left-click the screen over the center of it, but without first ;; exposing it. So if another window covers it, then the click ;; will go to that window instead of it. Pre-expose defaults to ;; t to ensure that the click goes to the specified window, but ;; it may be useful to pass it as nil as in this example if you ;; are testing that that the window is exposed when it should be. (do-click it :pre-expose nil) ;; Keypresses ;; Type a "j" into it. (do-keypress it #\j) ;; Type a semicolon into whatever window has the focus already. (do-keypress nil vk-semicolon) ;; Give it the focus and press down the shift key ;; without releasing it. WARNING: Doing this without ;; a subsequent up-click of the same key leaves the OS thinking ;; that the shift key is still down, and a further keystroke ;; will believe it is shifted. This can be fixed interactively ;; by simply pressing and releasing the left shift key. (do-keypress it vk-shift :up nil) ;; Type control-J into it. (do-keypress it #\j :control t) ;; Type a whole string of characters into it. (do-keypresses it "How about that.") ;; Print an arbitrary object into it. (do-keypresses it (list :one "Foo")) |# ;; ------------------------------------------------------------ ;; mouse events (defconstant win::MOUSEEVENTF_MOVE #x0001) (defconstant win::MOUSEEVENTF_LEFTDOWN #x0002) (defconstant win::MOUSEEVENTF_LEFTUP #x0004) (defconstant win::MOUSEEVENTF_RIGHTDOWN #x0008) (defconstant win::MOUSEEVENTF_RIGHTUP #x0010) (defconstant win::MOUSEEVENTF_MIDDLEDOWN #x0020) (defconstant win::MOUSEEVENTF_MIDDLEUP #x0040) (defconstant win::MOUSEEVENTF_WHEEL #x0800) (defconstant win::MOUSEEVENTF_ABSOLUTE #x8000) (ff:def-foreign-call (win::mouse_event "mouse_event") ((win::dwFlags win::dword) (win::dx win::dword) (win::dy win::dword) (win::dwData win::dword) (win::dwExtraInfo win::dword))) ;; officially ulong_ptr (defgeneric do-click (window-or-widget-or-nil &key (position (and window-or-widget-or nil :center)) (button :left)(pre-expose t) (preview-seconds 0.5)(down-seconds 0.5)) (:documentation "Simulates clicking a mouse button at some position in a window.")) (defmethod do-click ((widget dialog-item) &key (position (and widget :center)) (button :left)(pre-expose t) (preview-seconds 0.5)(down-seconds 0.5)) (let* ((window (window widget))) (when (windowp window) (do-click window :position position :button button :pre-expose pre-expose :preview-seconds preview-seconds :down-seconds down-seconds)))) (defmethod do-click ((window t) &key (position (and window :center)) (button :left)(pre-expose t) (preview-seconds 0.5)(down-seconds 0.5) (down t)(up t)) (unless window (setq window (screen *system*))) (let* ((win window) (down-event (and down (case button (:left win::MOUSEEVENTF_LEFTDOWN) (:middle win::MOUSEEVENTF_MIDDLEDOWN) (:right win::MOUSEEVENTF_RIGHTDOWN)))) (up-event (and up (case button (:left win::MOUSEEVENTF_LEFTUP) (:middle win::MOUSEEVENTF_MIDDLEUP) (:right win::MOUSEEVENTF_RIGHTUP)))) (stream-pos (case position (:center (box-center (visible-box window))) (t position))) #+not-used (screen-pos (and stream-pos (window-to-screen-units window (stream-to-window-units window (copy-position stream-pos)))))) (when window (when pre-expose (loop (unless (windowp win)(return)) (unless (eq win (selected-window (parent win))) (select-window win)) (setq win (parent win)))) ;; Move the mouse over the window. (when stream-pos (setf (cursor-position window) stream-pos)) #+no ;; These units apparently would need to be normalized ;; where 0 to 65k covers the screen, so use (setf cursor-position) ;; instead in order to use pixel units. (when screen-pos (win::mouse_event (logior win::MOUSEEVENTF_MOVE win::MOUSEEVENTF_ABSOLUTE) (position-x screen-pos) (position-y screen-pos) 0 0))) ;; Wait a bit for the user to see the window before the click is done. (when preview-seconds (sleep preview-seconds)) ;; Send the click down and up messages, pausing a bit in between ;; so that a human can see that the button was clicked. (when down (win::mouse_event down-event 0 0 0 0) #+no ;; The coordinates don't matter when doing the click, ;; though the MSDN doesn't make this clear. (win::mouse_event (logior down-event (if screen-pos win::MOUSEEVENTF_ABSOLUTE 0)) (if screen-pos (position-x screen-pos) 0) (if screen-pos (position-y screen-pos) 0) 0 0)) #+old ;; This works, but mouse_event may be more robust. (win:SendMessage (handle window) win:WM_LBUTTONDOWN win:MK_LBUTTON (win:makelong (position-x window-pos) (position-y window-pos))) (when down-seconds (sleep down-seconds)) (when up (win::mouse_event up-event 0 40000 0 0)) )) ;; ------------------------------------------------------------ ;; key presses (defconstant win::KEYEVENTF_EXTENDEDKEY 1) (defconstant win::KEYEVENTF_KEYUP 2) (ff:def-foreign-call (win::keybd_event "keybd_event") ((win::bVk byte) (win::bScan byte) (win::dwFlags win::dword) (win::dwExtraInfo win::dword)) :convention :stdcall :release-heap :when-ok :arg-checking nil :returning :void) (defgeneric do-keypress (window-or-widget-or-nil keynum-or-character &key (preview-seconds 0.5) shift control alt (down-seconds 0.5)(down t)(up t)) (:documentation #.(format nil "Simulates pressing and/or releasing a key ~ on the keyboard while some window has the focus."))) (defmethod do-keypress ((widget dialog-item)(keynum-or-char t) &key (preview-seconds 0.5)(down-seconds 0.5) shift control alt (down t)(up t)) (let* ((window (window widget))) (when (windowp window) (do-keypress window keynum-or-char :down-seconds down-seconds :preview-seconds preview-seconds :shift shift :control control :alt alt :down down :up up)))) (defmethod do-keypress ((window t)(keynum integer) &key (preview-seconds 0.5)(down-seconds 0.5) shift control alt (down t)(up t)) (declare (ignore shift control alt)) ;; Expose the window and its parents all the way up (when window (let* ((win window)) (loop (unless (windowp win)(return)) (unless (eq win (selected-window (parent win))) (select-window win)) (setq win (parent win)))) ;; Make sure the window has the keyboard focus. (unless (eq window (get-focus (screen *system*))) (win:SetFocus (handle window)))) ;; Wait a bit for the user to see the window before the click is done. (when preview-seconds (sleep preview-seconds)) ;; Send the click down and up messages, pausing a bit in between ;; so that a human can see any effect of the key being down. (when down (win::keybd_event keynum 0 0 0) #+old ;; this doesn't seem to work (win:SendMessage (handle window) win:WM_KEYDOWN keynum 1)) ;; "repeat" the keypress one time (when down-seconds (sleep down-seconds)) (when up (win::keybd_event keynum 0 win::KEYEVENTF_KEYUP 0) #+old (win:SendMessage (handle window) win:WM_KEYUP keynum (logior (expt 2 31) ;; transition flag (expt 2 30) ;; flag that key was down 1)) ;; "repeat" the keypress one time )) (defmethod do-keypress ((window t)(char character) &key (preview-seconds 0.5)(down-seconds 0.5) shift control alt (down t)(up t)) (let* ((vk (win:VkKeyScan (char-int char))) (key-number (cg::lobyte vk)) (shift-keys (cg::hibyte vk)) (upper-case? (logbitp 0 shift-keys))) (when preview-seconds (sleep preview-seconds)) (when alt (do-keypress window vk-alt :up nil :preview-seconds nil :down-seconds nil)) (when control (do-keypress window vk-control :up nil :preview-seconds nil :down-seconds nil)) (when (or shift upper-case?) (do-keypress window vk-shift :up nil :preview-seconds nil :down-seconds nil)) (do-keypress window key-number :preview-seconds nil :down-seconds down-seconds :down down :up up) (when (or shift upper-case?) (do-keypress window vk-shift :down nil :preview-seconds nil :down-seconds nil)) (when control (do-keypress window vk-control :down nil :preview-seconds nil :down-seconds nil)) (when alt (do-keypress window vk-alt :down nil :preview-seconds nil :down-seconds nil)) )) (defmethod do-keypresses ((window t)(object string) &key (preview-seconds 0.5)(down-seconds 0.5)) (let* ((length (length object))) (dotimes (j length) (do-keypress window (aref object j) :preview-seconds (if (eq j 0) preview-seconds nil) :down-seconds (if (eq j (1- length)) down-seconds))))) (defmethod do-keypresses ((window t)(object symbol) &key (preview-seconds 0.5)(down-seconds 0.5)) (do-keypresses window (symbol-name object) :preview-seconds preview-seconds :down-seconds down-seconds)) (defmethod do-keypresses ((window t)(object t) &key (preview-seconds 0.5)(down-seconds 0.5)) (do-keypresses window (princ-to-string object) :preview-seconds preview-seconds :down-seconds down-seconds)) ;;; Any update in external sources will be propagated after the next ;;; production cycle. Calling (update-activation-spread) will cause the ;;; spreading to occur immediately, with unspecified results if ACT-R is ;;; in the middle of the production-matching phase. (defvar *external-sources* nil) (let ((*warn-if-redefine* nil)) (defun update-activation-spread (&key (focus *wmfocus*) (external *external-sources*)) "Updates the activation sources to be the slot values of the focus wme. Also add the external sources if defined as list of (source . level)." (incf *spread-stamp* 1) (dolist (source *activation-sources*) (setf (wme-source source) nil)) (setf *activation-sources* nil) (when focus (let ((level (first (wme-slot-wmes focus)))) (when (> level 0.0) (setf level (/ *goal-activation* level)) (dolist (source (rest (wme-slot-wmes focus))) (cond ((wme-source source) (incf (wme-source source) level)) (t (setf (wme-source source) level) (push source *activation-sources*))))))) (when external (dolist (pair external) (let* ((object (car pair)) (level (cdr pair)) (sources (wme-slot-wmes object))) (when (> (first sources) 0.0) (setf level (/ level (first sources))) (dolist (source (rest sources)) (cond ((wme-source source) (incf (wme-source source) level)) (t (setf (wme-source source) level) (push source *activation-sources*))))))))) ) (defun external-sources-fct (&optional (sources-and-levels nil)) "Sets each external source to its level. If level is nil (or not a number), removes the source. If the (first) source is nil, removes all sources. If no argument is given, then does nothing. Returns the list of external sources." (when sources-and-levels (if (null (first sources-and-levels)) (setf *external-sources* nil) (loop (let* ((source (pop sources-and-levels)) (level (pop sources-and-levels))) (setf source (get-safe-wme source)) (when source (if (numberp level) (let ((pair (member source *external-sources* :test #'eq :key #'car))) (if pair (rplacd (first pair) level) (push (cons source level) *external-sources*))) (setf *external-sources* (delete source *external-sources* :test #'eq :key #'car))))) (unless sources-and-levels (return))))) *external-sources*) (defmacro external-sources (&rest sources-and-levels) "Sets each external source to its level. If level is nil (or not a number), removes the source. If the (first) source is nil, removes all sources. If no argument is given, then does nothing. Returns the list of external sources." `(external-sources-fct ',sources-and-levels)) #| ? (external-sources) NIL ? (external-sources pega 0.35 pegb 0.5) ((PEGB . 0.5) (PEGA . 0.35)) ? (external-sources pegc 0.65 pegb 0.55) ((PEGC . 0.65) (PEGB . 0.55) (PEGA . 0.35)) ? (external-sources pega 0.5 pegb nil) ((PEGC . 0.65) (PEGA . 0.5)) ? (external-sources nil) NIL |# ;;; -*- mode: LISP; Package: CL-USER; Syntax: COMMON-LISP; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Author : Mike Byrne ;;; Copyright : (c)1998-2002 CMU/Rice U./Mike Byrne, All Rights Reserved ;;; Availability: public domain ;;; Address : Rice University ;;; : Psychology Department ;;; : Houston,TX 77251-1892 ;;; : byrne@acm.org ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Filename : actr-interface.lisp ;;; Version : 2.1b4 ;;; ;;; Description : Formerly "act-interface.lisp" ;;; : This file contains functions which are ACT-R specific, ;;; : particularly in how DM is handled. ;;; ;;; Bugs : [] No way to send SCALE info with default move-attention ;;; : command. ;;; ;;; Todo : ;;; ;;; ----- History ----- ;;; 01.07.27 mdb ;;; : Started 5.0 conversion work. ;;; : [x] MOVE-ATTENTION can fail to generate a chunk {this might ;;; : go into the actr-interface.lisp} ;;; 01.08.07 mdb ;;; : Added a chunk type for START-TRACKING. Added SIZE slot on ;;; : VISUAL-LOCATION chunk type. ;;; 01.09.17 mdb ;;; : * Fixed glitch in :after methods for new-*-sound. ;;; : * Returned MIN-RUN-TIME. ;;; 02.01.21 mdb ;;; : Changed the proc-state part of state-spec. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Functions that define the relevant chunk types and such for ACT-R. ;;;; ---------------------------------------------------------------------- ;;;; (defun def-pm-chunk-types () "Defines chunk types used by the PM layer" (unless (get-type 'visual-location) ;; define the dmetype for state chunks (chunk-type module-state module modality processor preparation execution last-command) ;; visual interface chunks (chunk-type visual-object screen-pos value status color height width) (chunk-type abstract-object value line-pos bin-pos) (chunk-type (abstract-letter (:include abstract-object))) (chunk-type (abstract-number (:include abstract-object))) (chunk-type (text (:include visual-object))) (chunk-type (empty-space (:include visual-object))) #| This is really never used and requires lists, so this was removed for 2.1 (chunk-type (sub-object (:include visual-object)) objects) (chunk-type (point (:include sub-object))) (chunk-type (pair (:include sub-object))) (chunk-type (triplet (:include sub-object))) |# (chunk-type (line (:include visual-object)) other-pos end1-x end1-y end2-x end2-y) (chunk-type (oval (:include visual-object))) (chunk-type (cursor (:include visual-object))) (chunk-type (phrase! (:include visual-object)) objects words) (chunk-type visual-location screen-x screen-y attended kind color value size nearest) (chunk-type vision-command) (chunk-type motor-command) (chunk-type audio-command) (chunk-type pm-constant) (chunk-type color) ;; audio chunks (chunk-type audio-event attended onset offset pitch kind objects) (chunk-type sound kind content) ;;; Adds the chunk types to hold the various actions as subtypes of motor-command ;;; Note that move-mouse is currently a chunk of type motor-command but the ;;; documented motor commands (in 1.0 manual) such as press-key etc are just plain chunks. (chunk-type (click-mouse (:include motor-command))) (chunk-type (hand-to-mouse (:include motor-command))) (chunk-type (hand-to-home (:include motor-command))) (chunk-type (move-cursor (:include motor-command)) object loc device) (chunk-type (peck (:include motor-command)) hand finger r theta) (chunk-type (peck-recoil (:include motor-command)) hand finger r theta) (chunk-type (point-hand-at-key (:include motor-command)) hand to-key) (chunk-type (press-key (:include motor-command)) key) (chunk-type (punch (:include motor-command)) hand finger) ;;; Also for speech command(s) (chunk-type speech-command) (chunk-type (speak (:include speech-command)) string) ;;; Also for visual commands (chunk-type (start-tracking (:include vision-command)) object) )) ;;; add some state chunks to DM (defun add-pm-state-chunks () "Adds chunks for various RPM states." (unless (no-output (dm vision-state)) (no-output (add-dm-fct '((free isa chunk) (busy isa chunk) (vision-state isa module-state module :VISION modality FREE processor FREE preparation FREE execution FREE) (motor-state isa module-state module :MOTOR modality FREE processor FREE preparation FREE execution FREE) (speech-state isa module-state module :SPEECH modality FREE processor FREE preparation FREE execution FREE) (audio-state isa module-state module :AUDIO modality FREE processor FREE preparation FREE execution FREE) (move-mouse isa motor-command) (click-mouse isa motor-command) (find-location isa vision-command) (move-attention isa vision-command) (punch isa motor-command) (press-key isa motor-command) (listen-for isa audio-command) (black isa color) (digit isa chunk) (speech isa chunk) (tone isa chunk) (oval isa chunk) (click-mouse isa chunk) (text isa chunk) (box isa chunk) (move-cursor isa chunk) (move-attention isa chunk) (punch isa chunk) (peck isa chunk) (peck-recoil isa chunk) (hand-ply isa chunk) (point-hand isa chunk) (press-key isa chunk) (point-hand-at-key isa chunk) (hand-to-mouse isa chunk) (hand-to-home isa chunk) (:motor isa chunk) (:vision isa chunk) (:audio isa chunk) (:speech isa chunk) (lowest isa pm-constant) (highest isa pm-constant) (greater-than-current isa pm-constant) (less-than-current isa pm-constant) (current isa pm-constant) ) :reset-ia nil)))) (def-pm-chunk-types) (add-pm-state-chunks) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; ACT-R methods for the Declarative Memory Interface ;;;; ---------------------------------------------------------------------- ;;;; (defmethod make-dme (id (dmtype symbol) (attrs list) &key (obj nil) (where nil)) (declare (ignore where)) (no-output (add-dm-fct `((,id isa ,dmtype ,@attrs)) :reset-ia nil)) (make-instance 'dmo :ps-ptr (get-safe-wme id) :dmtype dmtype :id id :pm-obj obj)) (defmethod get-attribute ((self dmo) (attrname symbol)) (no-output (chunk-slot-value-fct (ps-ptr self) attrname))) (defmethod set-attributes ((self dmo) (attrs list)) (no-output (mod-chunk-fct (ps-ptr self) attrs)) self) (defmethod psdme-to-dmo (psdme) (setf psdme (get-safe-wme psdme)) (make-instance 'dmo :ps-ptr psdme :dmtype (wme-type psdme) :id (wme-name psdme))) (defmethod dmo-to-psdme ((self dmo)) (no-output (get-safe-wme (id self)))) ; (ps-ptr self)) (defmethod matching-dmos ((dmtype symbol) (attrs list)) (let ((psdmes (no-output (sdm-fct `(isa ,dmtype ,@attrs))))) (when psdmes (mapcar #'psdme-to-dmo psdmes)))) #| ;; mdb: What's this doing here? (defmethod xy-to-dmo :around ((loc list) (attended-p symbol)) (let ((the-dmo (call-next-method))) (no-output (add-reference (wme-references (dmo-to-psdme the-dmo))) (update-activation-spread)) the-dmo)) |# ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Method overrides and specializers for ACT-R ;;;; ---------------------------------------------------------------------- ;;;; ;;; could this be an :after method? (defmethod find-location :around ((vis-mod vision-module) &key (kind :IGNORE) (attended :IGNORE) (value :IGNORE) (color :IGNORE) (size :IGNORE) screen-x screen-y nearest) (declare (ignore kind attended value color size screen-x screen-y nearest)) (aif (call-next-method) (setf *visual-location* it) (setf *visual-location* (get-wme 'FAILURE)))) (defmethod new-sound-event :around ((evt sound-event)) (let ((evt (call-next-method))) (pm-timed-event (detect-at-time evt) #'stuff-sound-buffer))) (defun stuff-sound-buffer () (unless *aural-location* (find-sound-fct :attended nil))) ;;; again, coul this be :after? (defmethod find-sound :around ((aud-mod audio-module) &key (attended :IGNORE) (kind :IGNORE) onset pitch) (declare (ignore attended kind onset pitch)) (aif (call-next-method) (setf *aural-location* it) (setf *aural-location* (get-wme 'FAILURE)))) (defmethod process-display :after ((devin device-interface) (vis-mod vision-module) &optional (clear nil)) (declare (ignore devin clear)) (stuff-visloc-buffer vis-mod)) (defmethod min-run-time ((mstr-proc master-process)) *default-action-time*) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Functions to interface with ACT-R's modified production syntax ;;;; ---------------------------------------------------------------------- ;;;; ;;; vision (defun current-visual-object () "Return the chunk corresponding to the currently-attended object." (aif (currently-attended (vis-m *mp*)) (dmo-to-psdme it) (get-wme 'failure))) ; mdb 01.07.27 (defun find-location-fct (&key (kind :IGNORE) (attended :IGNORE) (value :IGNORE) (color :IGNORE) (size :IGNORE) (screen-x :IGNORE) (screen-y :IGNORE) nearest) "Calls the Vision Module's FIND-LOCATION method" (setf kind (check-gwn kind)) (setf attended (check-gwn attended)) (setf value (check-gwn value)) (setf color (check-gwn color)) (setf size (check-gwn size)) (setf screen-x (check-gwn screen-x)) (setf screen-y (check-gwn screen-y)) (setf nearest (check-gwn nearest)) (find-location (vis-m *mp*) :kind kind :attended attended :value value :color color :size size :screen-x screen-x :screen-y screen-y :nearest nearest)) (defmethod stuff-visloc-buffer ((vis-mod vision-module)) (unless *visual-location* (awhen (find-current-locs-with-spec vis-mod (default-spec vis-mod)) (setf *visual-location* (construct-location vis-mod (random-item (objs-max-slotval it 'tstamp)) (default-spec vis-mod)))))) ;;; audition (defun find-sound-fct (&rest params) "Call's the Audio Manager's FIND-SOUND method." (apply #'find-sound (audio-m *mp*) params)) (defun current-sound-fct () "Return the chunk corresponding to the currently-attended object." (awhen (currently-attended (audio-m *mp*)) (dmo-to-psdme it))) ;;; module state (defun module-state-fct (&key module (modality :ignore) (preparation :ignore) (execution :ignore) (processor :ignore) (last-command :ignore)) "Function to check the state of a PM module, for the ACT-R LHS test." (when (match-spec-p (make-instance 'pm-state-spec :modality modality :preparation preparation :execution execution :processor processor :last-command last-command) (key->mgr *mp* module)) (dmo-to-psdme (state-dmo (key->mgr *mp* module))))) (defclass pm-state-spec (pm-module spec) () (:default-initargs :check-slots #(modality-state preparation-state execution-state processor-state last-command) :modality :IGNORE :preparation :IGNORE :execution :IGNORE :processor :IGNORE :last-command :IGNORE)) #| (defmethod match-spec-p :around ((ts pm-state-spec) (mod pm-module)) (when (call-next-method) (let ((condition (proc-stub ts)) (value (proc-s mod))) (cond ((eq condition :IGNORE) T) ((functionp condition) (funcall condition value)) (t (equal condition value)))))) (defmethod match-spec-p :around ((ts pm-state-spec) (mod pm-module)) (call-next-method)) |# ;;; New code to patch up the compiler to act-r/pm ;;; ACT-R 5.0p1 (defvar *pm-calls* '((visual-location find-location-fct (attended :attended) (screen-x :screen-x) (screen-y :screen-y) (value :value) (kind :kind) (color :color) (size :size) (nearest :nearest) (time)) (audio-event find-sound-fct (attended :attended) (onset :onset) (offset :offset) (pitch :pitch) (kind :type) (objects) (time)) (module-state module-state-fct (module :module) (preparation :preparation) (processor :processor) (execution :execution) (modality :modality) (last-command :last-command)) (visual-object current-visual-object) (sound current-sound-fct)) "Mapping between chunk types and fcts and if applicable slot names and keyword arguments for calls to the perceptual/motor layer.") (defun sub-eval-vars (expr bindings &optional (fct-call t)) "Substitute stack reference for production variables in evaled expressions. Return a function to be funcalled rather than a form to be evaled, which for all but the simplest ones causes the compiler to be called every time, unless *compile-eval-calls* has been turned off." (let ((new-expr (list (copy-tree expr)))) (dolist (binding bindings (if fct-call (if *compile-eval-calls* (compile nil `(lambda () ,(first new-expr))) (eval `(function (lambda () ,(first new-expr))))) (first new-expr))) (nsubst `(get-wme-name (svref *instantiation* ,(variable-index binding))) (variable-name binding) new-expr :test #'eq)))) (defun compile-lhs (production lhs initializations bindings index) "Compiles the left-hand side of production. Add the instantiation-adding call." (let ((code nil) (form nil)) (dolist (clause lhs) (let ((key (pop clause)) (type nil) (tests nil) (binds nil) (slot nil) (value nil) (negation nil)) (setf form (cond ((retrievalp key) (setf key (var>var key)) (with-binding binding direct key bindings index (set-variable-type production type key (pop clause)) (when type (cond ((and (member (wme-type-name type) '(visual-location audio-event) :test #'eq) (member '(time now) clause :test #'equal)) ;; compatibility with the old hack (setf clause (delete '(time now) clause :test #'equal)) (setf (variable-type binding) type) (let* ((fct-args (rest (assoc (wme-type-name type) *pm-calls* :test #'eq))) (pm-call (list (first fct-args))) (argument-alist (rest fct-args))) (dolist (slot-value clause) (let* ((negation (when (equal (first slot-value) '-) (pop slot-value))) (slot (first slot-value)) (value (second slot-value)) (mapping (assoc slot argument-alist))) (cond (mapping (when (and (or (not (variablep value)) (get-variable-binding value bindings)) (second mapping)) (setf value (sub-eval-vars value bindings nil)) (nconc pm-call (list (second mapping) (if negation (list 'pm-not-equal value) value))))) (t (signal-warn "SLOT ~S OF CHUNK ~S TYPE ~S IN PRODUCTION ~S IS UNDEFINED." slot key type production))))) (compile-lhs-slots tests binds) (list (if (and *enable-rational-analysis* code) ; the goal cannot be partial-matched 'external-test-and-bind-pm 'external-test-and-bind) (variable-index binding) pm-call type nil binds))) ((or (member key '(=visual-state =aural-state =manual-state =vocal-state)) ;; reserved keywords for states (eq (wme-type-name type) 'module-state)) (setf clause (delete '(time now) clause :test #'equal)) (setf (variable-type binding) type) (let* ((fct-args (rest (assoc (wme-type-name type) *pm-calls* :test #'eq))) (pm-call (list (first fct-args))) (argument-alist (rest fct-args))) (when (and (member key '(=visual-state =aural-state =manual-state =vocal-state)) (not (assoc 'module clause))) (nconc pm-call (list :module (case key (=visual-state :vision) (=aural-state :audio) (=manual-state :motor) (=vocal-state :speech))))) (dolist (slot-value clause) (let* ((negation (when (equal (first slot-value) '-) (pop slot-value))) (slot (first slot-value)) (value (second slot-value)) (mapping (assoc slot argument-alist))) (cond (mapping (when (and (or (not (variablep value)) (get-variable-binding value bindings)) (second mapping)) (setf value (sub-eval-vars value bindings nil)) (nconc pm-call (list (second mapping) (if negation (list 'pm-not-equal value) value))))) (t (signal-warn "SLOT ~S OF CHUNK ~S TYPE ~S IN PRODUCTION ~S IS UNDEFINED." slot key type production))))) (compile-lhs-slots tests binds) (list 'buffer-test-and-bind ;; It is all a buffer match now (variable-index binding) pm-call type nil binds))) ((or (member key '(=visual =aural)) ;; reserved keywords for visual and sound objects (and (or (member 'visual-object (wme-type-supertypes type) :test #'eq :key #'wme-type-name) (member 'sound (wme-type-supertypes type) :test #'eq :key #'wme-type-name)) (member '(time now) clause :test #'equal))) (setf clause (delete '(time now) clause :test #'equal)) (setf (variable-type binding) type) (compile-lhs-slots tests binds) (list 'buffer-test-and-bind ;; It is all a buffer match now (variable-index binding) (list (second (assoc (if (or (member 'visual-object (wme-type-supertypes type) :test #'eq :key #'wme-type-name) (eq key '=visual)) 'visual-object 'sound) *pm-calls* :test #'eq))) type tests binds)) ((member key '(=manual =manual-location =vocal =vocal-location)) (signal-warn "RESERVED KEYWORD NOT YET IMPLEMENTED.")) (t ;; includes =visual-location and =aural-location because they are now buffers (setf (variable-type binding) type) (compile-lhs-slots tests binds) (list (if (assoc (variable-index binding) initializations :test #'=) ;; Fixed buffers have their special test to avoid activation 'direct-test-and-bind-buffer (if *enable-rational-analysis* (if direct 'direct-test-and-bind-pm 'indirect-test-and-bind-pm) (if direct 'direct-test-and-bind 'indirect-test-and-bind))) (variable-index binding) type tests binds)))))) ((eq '!eval! key) (list 'eval-test (sub-eval-vars (first clause) bindings))) ((eq '!bind! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list 'bind-lhs (variable-index binding) (sub-eval-vars (second clause) bindings)))) ((eq '!find-location! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list 'bind-find-location (variable-index binding) (sub-eval-vars (cons 'list (quote-arguments (rest clause))) bindings)))) (t (signal-warn "UNKNOWN COMMAND ~S IN PRODUCTION ~A." key production)))) (setf code (nconc code form)))) ;; FIX: split into goal and retrievals when ERA is enabled. (values (if *enable-rational-analysis* (let ((direct-length (first-retrieval-index code initializations))) (cons (nconc (subseq code 0 direct-length) (list 'add-instantiation-to-conflict-set)) (nconc (subseq code direct-length) (list 'not)))) (nconc code (list 'add-instantiation-to-conflict-set))) bindings index))) (defun buffer-test-and-bind (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. Returns t if a complete match is found, nil otherwise." (let* ((wme-index (pop arguments)) ;; FIX: get the current visual or sound object by calling the PM layer ;; If the variable is already bound, test that it is identical (external-call (pop arguments)) (wme (apply (first external-call) (rest external-call))) (binding (instantiation-variable *instantiation* wme-index)) (wmetype (pop arguments)) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (cond ((and binding (not (eq wme binding))) (signal-output *exact-matching-trace* "Current visual object ~S does not match existing binding ~S." wme binding)) (t (unless binding (setf (instantiation-variable *instantiation* wme-index) wme)) (if (and (wmep wme) (subtype (wme-type wme) wmetype)) (let ((slots (wme-slots wme))) ;; Set bindings before testing slots (and (set-bindings slots slots-bound) ; ;; FIX: The retrieval threshold does not apply to PM calls ; (let ((*retrieval-threshold* nil)) ; (test-slots wme slots slots-tested wme-index)) (test-slots-buffer wme slots slots-tested) (simulate-call arguments))) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype)))))) ;;; added back for compatibility with the old time now hack (defun external-test-and-bind (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. Returns t if a complete match is found, nil otherwise." (let* ((wme-index (pop arguments)) ;; FIX: get the current visual or sound object by calling the PM layer ;; If the variable is already bound, test that it is identical (external-call (pop arguments)) (wme (apply (first external-call) (rest external-call))) (binding (instantiation-variable *instantiation* wme-index)) (wmetype (pop arguments)) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (cond ((and binding (not (eq wme binding))) (signal-output *exact-matching-trace* "Current visual object ~S does not match existing binding ~S." wme binding)) (t (unless binding (setf (instantiation-variable *instantiation* wme-index) wme)) (if (and (wmep wme) (subtype (wme-type wme) wmetype)) (let ((slots (wme-slots wme))) ;; Set bindings before testing slots (and (set-bindings slots slots-bound) ;; FIX: The retrieval threshold does not apply to PM calls (let ((*retrieval-threshold* nil)) (test-slots wme slots slots-tested)) ;; wme-index)) (simulate-call arguments))) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype)))))) (defun external-test-and-bind-pm (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. This is the rational analysis version, which handles partial matching too." (let* ((wme-index (pop arguments)) ;; FIX: get the current visual or sound object by calling the PM layer ;; If the variable is already bound, test that it is identical (external-call (pop arguments)) (wme (apply (first external-call) (rest external-call))) (binding (instantiation-variable *instantiation* wme-index)) (wmetype (pop arguments)) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (cond ((and binding (not (eq wme binding))) (signal-output *exact-matching-trace* "Current visual object ~S does not match existing binding ~S." wme binding)) ((and (wmep wme) (subtype (wme-type wme) wmetype)) (unless binding (setf (instantiation-variable *instantiation* wme-index) wme)) (let* ((slots (wme-slots wme)) ;; Set bindings before testing slots (activation (and (set-bindings slots slots-bound) ;; FIX: The retrieval threshold does not apply to PM calls (let ((*retrieval-threshold* most-negative-short-float)) (if *partial-matching* (test-slots-pm wme slots slots-tested) (test-slots wme slots slots-tested)))))) ;; FIX: Do not increment the latency ;; (add-latency wme (or activation *retrieval-threshold*)) (when activation (simulate-call arguments)))) (t ;; FIX: Do not increment the latency ;; (add-latency wme *retrieval-threshold*) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype))))) (defparameter *rhs-pm-calls* '((visual-object (:vision move-attention) (screen-pos :location) (scale :scale)) (visual-location (:vision find-location) (attended :attended) (screen-x :screen-x) (screen-y :screen-y) (value :value) (kind :kind) (color :color) (size :size) (nearest :nearest) (time)) (start-tracking ; 01.11.10 mdb (:vision start-tracking) ; missing entry added [again] (object nil)) (click-mouse (:motor click-mouse)) (hand-to-mouse (:motor hand-to-mouse)) (hand-to-home (:motor hand-to-home)) (move-cursor (:motor move-cursor) (object :object) (loc :loc) (device :device)) (peck (:motor peck) (hand :hand) (finger :finger) (r :r) (theta :theta)) (peck-recoil (:motor peck) (hand :hand) (finger :finger) (r :r) (theta :theta)) (point-hand-at-key (:motor point-hand-at-key) (hand :hand) (to-key :to-key)) (press-key (:motor press-key) (key nil)) (punch (:motor punch) (hand :hand) (finger :finger)) (speak (:speech speak) (string :text)) ; 01.11.10 mdb fixed entry (sound (:audio attend-sound) (event :event)) (audio-event (:audio find-sound) (attended :attended) (type :type) (onset :onset) (pitch :pitch)) )) (defmethod not-equal ((criterion number)) (lambda (val) (not (equal val criterion)))) (defmethod not-equal ((criterion list)) (not-equal (check-fct criterion))) (defun translate-comparison-tests (clause) (let ((new-clause nil)) (dolist (pair clause) (if (test-modifier (first pair)) (let ((modifier (first pair))) (cond ((or (eq modifier '>) (eq modifier '>=)) (push-last (list (second pair) (list 'greater-than (third pair))) new-clause)) ((or (eq modifier '<) (eq modifier '<=)) (push-last (list (second pair) (list 'less-than (third pair))) new-clause)) ((eq modifier '-) (push-last (list (second pair) (list 'not-equal (third pair))) new-clause)) (t (signal-warn "ILLEGAL TEST ~S APPLIED TO ~S ~S IN PRODUCTION ACTION." modifier (second pair) (third pair))))) (push-last pair new-clause))) (do ((rest-clause new-clause (rest rest-clause))) ((null rest-clause)) (let* ((first-pair (first rest-clause)) (other-pair (assoc (first first-pair) (rest rest-clause) :test #'eq))) (when other-pair (let ((arguments nil)) (when (listp (second first-pair)) (setf arguments (append arguments (second first-pair)))) (when (listp (second other-pair)) (setf arguments (append arguments (second other-pair)))) (when (and (member 'less-than arguments :test #'eq) (member 'greater-than arguments :test #'eq)) (rplacd first-pair (list (list 'within (min (second arguments) (fourth arguments)) (max (second arguments) (fourth arguments))))) (setf rest-clause (delete other-pair rest-clause))))))) new-clause)) (defun compile-rhs (production rhs bindings index) "Compiles the right-hand side of production." (let ((code nil) (returns nil) (goal-stack (list (variable-name (first bindings))))) (dolist (clause rhs) (let ((key (pop clause)) (type nil) (assigns nil) (slot nil) (value nil) (negation nil)) (setf code (nconc code (cond ((retrievalp key) (setf key (var>var key)) (when (actionp key) (setf key (var=var key))) ;; strip out the + (cond ((clearp key) ;; clearing buffers (setf key (var=var key)) ;; strip out the - (cond ((member key '(visual manual aural vocal)) ;; clearing modules (let ((command-call (list (case key (visual :vision) (aural :audio) (manual :motor) (vocal :speech)) 'clear))) (list (list 'action-command (sub-eval-vars (cons 'list (quote-arguments command-call)) bindings))))) ((assoc key *buffer-keywords*) ;; clearing ACT-R buffers (list (list 'assign-buffer key nil))) (t (signal-warn "UNKNOWN BUFFER ~S CANNOT CLEAR." key)))) ((and (or (assoc key *buffer-keywords*) ;; directly manipulate PM buffers (member key '(visual manual vocal aural =visual =manual =vocal =aural))) (null (rest clause)) (null (rest (first clause))) (listp (first (first clause)))) ;; assignment to buffer of result of function call (list (list 'assign-buffer key (sub-eval-vars (first (first clause)) bindings)))) ((and (not (variablep key)) (assoc key *buffer-keywords*) (null (rest clause)) (null (rest (first clause)))) ;; direct assignment to buffers, including goal focus and direct retrieval (let ((binding (get-variable-binding (first (first clause)) bindings))) (cond ((eq key 'goal) ;; focus on goal (setf goal-stack (butlast goal-stack)) (push-last key goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns key returns)))) ((eq key 'retrieval) ;; direct retrieval (list (list 'handle-failure (if *enable-rational-analysis* 'direct-test-and-bind-pm 'direct-test-and-bind) (variable-index binding) type nil nil 'assign-retrieval (variable-index binding)))) (t ;; other buffer assignment (list (list 'assign-buffer key (variable-index binding))))))) (t (with-binding binding modify key bindings index (cond (modify (setf type (variable-type binding)) (if (and type (not (integerp type))) (when (eq 'isa (caar clause)) (set-variable-type production type key (pop clause)) (unless (or (eq type (variable-type binding)) (assoc key *buffer-keywords*)) (signal-warn "TYPE OF VARIABLE ~S IS BEING REDEFINED IN PRODUCTION ~A." key production))) (set-variable-type production type key (pop clause)))) (t (set-variable-type production type key (pop clause)))) (when type (cond ((or (eq 'retrieval key) (member (list '!retrieve! key) rhs :test #'equal)) (let ((tests nil) (binds nil)) (compile-lhs-slots tests binds) (list (list 'handle-failure (if *enable-rational-analysis* (if modify 'direct-test-and-bind-pm 'indirect-test-and-bind-pm) (if modify 'direct-test-and-bind 'indirect-test-and-bind)) nil type tests binds 'assign-retrieval nil)))) ;;; Partial implementation of RHS buffers. ;;; Full implementation will require further standardization of RPM conventions ((member key '(visual manual visual-location vocal aural aural-location)) (let* ((fct-args (rest (assoc (wme-type-name type) *rhs-pm-calls* :test #'eq))) (command-call (copy-seq (first fct-args))) (argument-alist (rest fct-args))) (dolist (slot-value (translate-comparison-tests clause)) (let* ((slot (first slot-value)) (value (second slot-value)) (mapping (assoc slot argument-alist))) (if mapping (setf command-call (nconc command-call (if (second mapping) (list (second mapping) value) (list value)))) (signal-warn "SLOT ~S OF CHUNK ~S TYPE ~S IN PRODUCTION ~S IS UNDEFINED." slot key type production)))) (list (list 'action-command (sub-eval-vars (cons 'list (quote-arguments command-call)) bindings))))) ((member key '(visual-state manual-location manual-state vocal-location vocal-state aural-state)) (signal-warn "RESERVED KEYWORD ~A NOT YET IMPLEMENTED." key)) (t (compile-rhs-slots assigns returns binding) (cons (if modify (list 'modify-old-wme (variable-index binding) type assigns) (list 'create-new-wme (variable-index binding) (if (variablep key) (var=var key) key) type assigns)) (when (and (or (eq '=newgoal key) (eq 'newgoal key) (eq 'goal key)) (not (member (list '!push! key) rhs :test #'equal)) (not (member (list '!focus-on! key) rhs :test #'equal))) (setf goal-stack (butlast goal-stack)) (push-last key goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns key returns)))))))))))) ((eq '!retrieve! key) ;; do nothing since retrieval already done nil) ((eq '!push! key) (let* ((subgoal (first clause)) (binding (get-safe-variable-binding subgoal bindings production))) (when binding (push-last subgoal goal-stack) (list (list 'push-fct (variable-index binding) (subgoal-returns subgoal returns)))))) ((eq '!pop! key) ;;; FIX: pop from the end of the stack (setf goal-stack (butlast goal-stack)) (list (list 'pop-fct))) ((eq '!focus-on! key) (let* ((subgoal (first clause)) (binding (get-safe-variable-binding subgoal bindings production))) (when binding ;;; FIX: do not assume that there is something on the stack (setf goal-stack (butlast goal-stack)) (push-last subgoal goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns subgoal returns)))))) ((eq '!output! key) (list (list 'output (compile-output clause bindings production)))) ((eq '!eval! key) (list (list 'eval-side (sub-eval-vars (first clause) bindings)))) ((eq '!bind! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'bind-rhs (variable-index binding) (sub-eval-vars (second clause) bindings))))) ((eq '!move-attention! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'bind-move-attention (variable-index binding) (sub-eval-vars (cons 'list (quote-arguments (rest clause))) bindings))))) ((eq '!press-key! key) (list (list 'visual-action 'press-key (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!move-mouse! key) (list (list 'visual-action 'move-mouse (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!click-mouse! key) (list (list 'visual-action 'click-mouse (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!send-command! key) (list (list 'action-command (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!delete! key) (let ((binding (get-safe-variable-binding (first clause) bindings production))) (when binding (list (list 'delete-wme-variable (variable-index binding)))))) ((or (eq '!copy! key) (eq '!copywme! key)) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'copy-chunk-variable (variable-index binding) (sub-eval-vars (cons 'list (rest clause)) bindings))))) ((eq '!stop! key) (list (list 'stop))) ((eq '!restart! key) (list (list 'restart-top-goal))) (t (signal-warn "UNKNOWN COMMAND ~S IN PRODUCTION ~A." key production))))))) ; remove the unknown commands ; (setf code (delete nil code :test #'eq)) ; translate wme variables into relative goal stack indices for value returns (dolist (return returns) (let ((from-position (position (variable-name (variable-index return)) goal-stack :test #'eq))) (if from-position (dolist (destination (variable-returns return)) (let ((to-position (position (first destination) goal-stack :test #'eq))) (if (and (numberp to-position) (> from-position to-position)) (rplaca destination (- from-position to-position 1)) (signal-warn "VARIABLE ~A CANNOT BE RETURNED BECAUSE CHUNK ~A IS NOT ON THE STACK BELOW SUBGOAL ~A IN PRODUCTION ~A." (variable-name return) (first destination) (variable-name (variable-index return)) production)))) (signal-warn "VARIABLE ~A CANNOT BE RETURNED BECAUSE CHUNK ~A IS NOT ON THE STACK IN PRODUCTION ~A." (variable-name return) (variable-name (variable-index return)) production)))) (values code bindings index))) ;;; ;;; ACT-R 5.0 ;;; ;;; ;;; COPYRIGHT ;;; (defun act-r-copyright () (format t "~&ACT-R VERSION 5.0b0~%REPORT BUGS TO CL+@CMU.EDU~%~ +------------------------------------------------------------+~%~ | ACT-R 5.0 |~%~ | (c) 2000 |~%~ | Christian Lebiere John R. Anderson |~%~ | Department of Psychology, Carnegie Mellon University |~%~ | Supported by ONR Contract N00014-95-10223 |~%~ +------------------------------------------------------------+~%") (values)) (act-r-copyright) ;;; ;;; VARIABLE, STRUCTURE AND ACCESS MACRO DEFINITIONS ;;; ;;; Global System Variables (defvar *compile-eval-calls* nil "Do not compile !eval! and other Lisp calls at production definition.") (defvar *model* nil "Holds the file name of the current model and its contents. Set by clear-all and used by reset and reload.") (defvar *time* 0.0 "The real-time counter.") (defvar *start-time* 0.0 "The real-time counter.") (defvar *default-action-time* 0.05 "The default time to execute a right-hand side is 50 milliseconds.") (defvar *latency* 0.0 "Latency of conflict resolution process.") (defvar *cycle* 0 "The production firing cycle counter.") (defvar *spread-stamp* 0 "The stamp for activation spreading. Similar to *cycle*, but may clock faster due to interactive manipulation of sources, or slower when rational analysis is turned off.") (defvar *wme-number* 0.0 "Holds the count of the total number of wmes in declarative memory.") (defvar *declarative-memory* nil "Holds a list of wme types indexed by name, with for each type its definition (with possible subtypes) and the list of wmes of that type.") (defvar *hash-names* nil "Holds for each wme name a pointer to its structure. Primarily used to load the data base and for interfacing, not at run-time when direct pointers are used instead of names to hash.") (defvar *used-ACT-R-symbols* nil "Holds a list of all the temporary symbols interned by ACT-R so they can be freed on a reset.") (defvar *procedural-memory* nil "Holds a list of productions indexed by names.") (defvar *goal-activation* 1.0 "Amount of activation source level to be evenly split among goal elements.") (defvar *goal-sources* nil "Activation sources existing after the current goal is pushed.") (defvar *wmfocus* nil "The top goal.") (defvar *wmfocus-list* nil "The list of goals to focus on specified by the expanded form of wmfocus.") (defvar *goal-stack* nil "Holds the stack of goal, with the top-most at the front of the list.") (defvar *goal-depth* 1 "The depth of the goal stack, times 3 plus 1, to be used for indenting traces.") (defvar *g* 20.0 "The value of G in PG-C. Default is 20.0 for historical reasons.") (defvar *exp-gain-noise* nil "Variance of the zero-mean gaussian noise added to instantiation values.") (defvar *retrieval* nil "Holds the last chunk retrieved from declarative memory.") (defvar *retrieval-scheduler* nil "Holds the result of the next retrieval and the time at which it will be available.") (defvar *visual-location* nil "Holds the result of the last find-location.") (defvar *aural-location* nil "Holds the result of the last find-sound") (defvar *previous-instantiations* nil "The list of instantiations from the previous resetting.") (defvar *previous-instantiation* nil "The previous instantiation and its time for use in production compilation.") (defvar *instantiation* nil "The current instantiation as a global variable to limit parameter passing and allow references in eval statements.") (defvar *extra-instantiation* nil "List of previously generated instantiations of a given production, or instantiation to store activations if partial matching is on.") (defvar *conflict-set* nil "The conflict set holds the list of current instantiations.") (defvar *sum-exp-act* 0.0 "Holds the sum of the exponential of activations for scaling latencies.") (defvar *latency-fn* 'old-latency "The function used in computing retrieval latency from chunk(s) activation.") (defvar *latency-factor* 1.0 "The latency scaling factor B in the latency equation ROM (3.3).") (defvar *latency-exponent* 1.0 "The activation scaling factor b in the latency equation ROM (3.3).") (defvar *base-level-constant* 0.0 "The base level constant, i.e. B in ROM Equation 4.3") (defvar *activation-sources* nil "List of activation sources and their source level.") (defvar *activation-noise* nil "Variance of the zero-mean gaussian noise added to activation.") (defvar *permanent-activation-noise* nil "Variance of the zero-mean gaussian noise permanently added to activation.") (defvar *mismatch-penalty* 1.0 "Maximum mismatch penalty, in activation units, in partial matching.") (defvar *retrieval-threshold* nil "Minimum amount of activation needed for retrieval. Nil by default (N/A) but switch to default of 0.0 for partial matching.") (defvar *partial-matching* nil "Triggers partial matching.") (defvar *max-sim* 1.0 "Maximum similarity between two values, presumably in case of identity.") (defvar *max-dif* -10.0 "Maximum difference between two values, presumably for complete mismatches.") (defvar *temperature* 1.0 "When set (symbol :tmp) to a positive number, it is used instead of (sqrt 2 *) the activation noise as the scaling factor in the blending process.") (defvar *blending* nil "Triggers blending (symbol :bln). If set to 'rt, then blending occurs only after standard retrieval fails to reach threshold.") (defvar *blending-trace* nil "Triggers the blending trace (symbol :blt).") (defvar *blending-activation-fn* 'first-equation "Defines the activation of the blended value as a function of the activations of the individual chunks and the similarities between their values.") (defvar *break-productions* nil "Holds the productions on which to break before firing.") (defvar *failed-productions* nil "Holds the failed productions.") (defvar *cost-penalty* 1.0 "Cost Penalty for new productions.") (defvar *initial-experience* 10.0 "Initial Experience (total successes and failures) for new productions.") (defvar *threshold-time* 2.0 "Threshold of time between production firings to allow production rule learning.") (defvar *enable-production-learning* nil "Triggers production rule learning.") (defvar *reinforce-analogized-production* t "Whether or not to reinforce a production when it is re-analogized.") (defvar *production-compilation-parameters* nil "Holds the argument list to the parameters command for compiled productions") (defvar *enable-rational-analysis* nil "Triggers the activation and rational analysis computation.") (defvar *enable-randomness* nil "When on, ranks instantiations randomly instead of according to some deterministic but arbitrary order.") (defvar *utility-threshold* 0.0 "Minimum utility (pg-c) needed for production selection. 0.0 by default, but can be set to any number or even NIL when no thresholding is desired.") (defvar *optimized-learning* t "Mode of learning base levels and production strength which ensures constant space and time demands. Generalized to take a fixed number of references.") (defvar *base-level-learning* nil "The trigger for base level learning, d in ROM Equation 4.4. Should be less than 1.0 for the optimized learning formula to apply.") (defvar *associative-learning* nil "The trigger for associative learning, a in ROM Equation 4.5. Can be any non-negative value.") (defvar *strength-learning* nil "The trigger for learning production strength, d in ROM Equation 4.6. Should be less than 1.0 for the optimized learning formula to apply.") (defvar *parameters-learning* nil "The trigger for learning production parameters in ROM Equations 4.7 and 4.8. True or false.") (defvar *command-trace* t "Directs the output of the user-level commands. Not included in sgp.") (defvar *output-trace* t "Directs the printing of !output! command.") (defvar *cycle-trace* t "Prints the name of production to fire at every cycle.") (defvar *latency-trace* nil "Prints the latency of matching and action side of firing production.") (defvar *partial-matching-trace* nil "Prints all information relevant to partial matching.") (defvar *production-compilation-trace* t "Prints the trace of the production compilation process.") (defvar *activation-trace* nil "Prints the activation computation for wmes and instantiations.") (defvar *conflict-resolution-trace* nil "Prints the conflict resolution at work.") (defvar *conflict-set-trace* nil "Prints the number of instantiations considered out of the conflict set total, and the expected gain of the winning instantiation.") (defvar *goal-trace* nil "Prints goal pushing and popping.") (defvar *dm-trace* nil "Prints addition or deletion of wmes during execution.") (defvar *production-trace* nil "Prints a description of each production instantiation fired.") (defvar *matches-trace* nil "Prints a description of all instantiations.") (defvar *exact-matching-trace* nil "Prints a trace of every (non-partial) match.") (defvar *verbose* t "When off, turns off all but essential printing.") (defvar *abort-instantiation* t "Determines whether or not an instantiation can be aborted by a time limit or allowed to complete.") (defvar *parameter-sets* nil "Holds the parameter sets in a-list format.") (defvar *similarity-hook-fn* nil "If non-nil, call this function to get similarity values. If it returns nil, then the usual process of looking up similarities in wmes and comparing non-wmes for equality is used.") (defvar *linear-similarity-scale* 1.0 "Scale for linear-similarity: similarity = difference / scale.") (defvar *blending-hook-fn* 'blending-arithmetic-mean "Defines the direct averaging for number values.") (defvar *conflict-set-hook-fn* nil "If non-nil, call this function before conflict resolution with the conflict set. If it returns an instantiation, then fires that one, otherwise run as usual.") (defvar *firing-hook-fn* nil "if non-nil, called for side-effect with the instantiation before its firing.") (defvar *cycle-hook-fn* nil "If non-nil, call this function after each cycle with the instantiation fired.") (defvar *web-hook-fn* nil "If non-nil, call this function after each cycle with the instantiation fired. Same as *cycle-hook-fn*, but used specifically by ACT-R-on-the-web.") (defvar *end-run-hook-fn* nil "If non-nil, call this function at the end of a run with the total run latency.") (defvar *init-hook-fn* nil "if non-nil, call this function after clear-all with no arguments.") (defvar *step-fn* 'step-fct "If non-nil, call this function at each step of pstep to get user instruction.") (defvar *stop* nil "If non-nil, stops the run function before the next cycle.") (defvar *save-state-changes* nil "If non-nil, the state changes are saved for later undoing.") (defvar *pop-upon-failure* nil "If non-nil, then pop the focus when no suitable instantiation can be found. If nil, then stop the run as in previous versions without taking any action.") (defconstant *buffer-keywords* '((=goal . *wmfocus*) (goal . *wmfocus*) (=retrieval . *retrieval*) (retrieval . *retrieval*) (=visual-location . *visual-location*) (visual-location . *visual-location*) (=aural-location . *aural-location*) (aural-location . *aural-location*)) "Holds the alist of special buffers with their associated special variables. Works for both variable and constant names.") (defconstant *command-mappings* '((ACTIVATION-SOURCES ACTIVATIONSOURCES) (ACTR-TIME ACTRTIME) (ADD-DM ADDWM) (ADD-IA ADDIA) (CHUNK-SLOT-VALUE WMESLOTVALUE) (CHUNK-TYPE WMETYPE) (CLEAR-ALL CLEARALL) (CLEAR-DM CLEARWM) (CLEAR-GOAL-STACK CLEARGOALSTACK) (CLEAR-PRODUCTIONS CLEARPRODUCTIONS) (CLOSE-OUTPUT CLOSEOUTPUT) (CLOSE-TRACE CLOSETRACE) (COPY-CHUNK COPYWME) (DELETE-CHUNK DELETEWM) (DM WM) (FOCUS-ON-GOAL FOCUS-ON-WME) (GET-BASE-LEVEL GETBASELEVEL) (GOAL-FOCUS WMFOCUS) (GOAL-STACK GOALSTACK) (HELP NIL) (IA IA) (IMPORT-MODEL IMPORT-MODEL) (LOAD-MODEL LOAD-MODEL) (MOD-CHUNK MODWME) (MOD-FOCUS MODFOCUS) (NAME NAME) (NO-OUTPUT NO-OUTPUT) (OUTPUT-STREAM OUTPUTSTREAM) (P P) (PARAMETERS PARAMETERS) (PBREAK PBREAK) (PDISABLE PDISABLE) (PENABLE PENABLE) (PMATCHES PMATCHES) (POP-GOAL POP-WME) (PP PP) (PRODUCTION-PARAMETER PRODUCTION-PARAMETER) (PSET PSET) (PSTEP PSTEP) (PUNBREAK PUNBREAK) (PUNDO PUNDO) (PUSH-GOAL PUSH-WME) (RELOAD RELOAD) (RESET RESET) (RESET-IA RESETIA) (RUN RUN) (RUN-MANY RUN-MANY) (SDM SWM) (SDP SWP) (SET-ALL-BASE-LEVELS SETALLBASELEVELS) (SET-COMPILATION-PARAMETERS SETANALOGIZEDPARAMETERS) (SET-BASE-LEVELS SETBASELEVELS) (SET-DM SETWM) (SET-G SETG) (SET-GENERAL-BASE-LEVELS SETGENERALBASELEVELS) (SET-IA SETIA) (SET-SIMILARITIES SETSIMILARITIES) (SGP SGP) (SIMILARITY SIMILARITY) (SPP SPP) (TRACE-STREAM TRACESTREAM) (UPDATE-ACTIVATION UPDATE-ACTIVATION) (WHYNOT WHYNOT) (WHYNOT-DEPENDENCY WHYNOT-ANALOGY))) ;;; Error, Abort, Warning and Output functions. Plus Save State Changes. (defmacro signal-error (message &rest arguments) "Signals an error and stops." `(error ,message ,@arguments)) (defmacro signal-warn (message &rest arguments) "Outputs a warning of message and arguments." `(format *error-output* ,(concatenate 'string "~&~VT" message) ,@(cons '*goal-depth* arguments))) (defmacro signal-output (stream message &rest arguments) "Outputs message and arguments when verbose. Prints by default in lowercase." `(when (and *verbose* ,stream) (let ((*print-case* :downcase)) (format ,stream ,(concatenate 'string "~&~VT" message) ,@(cons '*goal-depth* arguments))))) (defmacro save-state-change (&rest change) "Save the state change." `(when *save-state-changes* (push (list ,@change) *save-state-changes*))) ;;; WME Types as structures (defun print-wme-type (wmetype stream depth) "Print a wme type as just its name, in uppercase." (declare (ignore depth)) (format stream "~:@(~A~)" (wme-type-name wmetype))) (defstruct (wme-type (:print-function print-wme-type)) "Holds the wmetype name, lists of subtypes and supertypes including itself as the first of each, the size of the wme (number of slots), a list of slots containing name, index and default value, a list of productions in which this type is used as goal type, and the list of wmes of that type. Also the documentation string." name subtypes supertypes size slots productions wmes documentation) ;;; Declarative Memory as an a-list of types (defmacro get-type (type) "Returns the structure corresponding to type." `(cdr (assoc ,type *declarative-memory* :test #'eq))) (defmacro get-safe-type (type &optional context) "Calls get-type, and prints an error if undefined." `(if (wme-type-p ,type) ,type (or (get-type ,type) (signal-warn "CHUNK TYPE ~S IS UNDEFINED IN ~A." ,type ,context)))) (defmacro for-all-wmes (wme &rest form) "Execute forms where wme cycles through all wmes in declarative memory." `(dolist (wme-type *declarative-memory*) (dolist (,wme (wme-type-wmes (cdr wme-type))) ,@form))) ;;; WMEs as structures (defun print-wme (wme stream depth) "Print a wme as just its name, capitalized." (declare (ignore depth)) (format stream "~:(~A~)" (wme-name wme))) (defstruct (wme (:print-function print-wme)) "WMEs are represented as structures, with the slots as a slot array." name type fan (slot-wmes (list 0.0)) ias similarities (creation-time *time*) (creation-cycle *cycle*) (time-stamp (- *time* 1.0)) (activation *base-level-constant*) (base-level *base-level-constant*) (spread-stamp (- *spread-stamp* 1.0)) (source-spread 0.0) (references (cons 1.0 (when (and *base-level-learning* (not (eq *optimized-learning* t))) (list *time*)))) (contexts 0.0) (needed 0.0) slots source (permanent-noise 0.0) documentation) (defmacro slots-slot (slots index) "Given the slots subarray, return the index value." `(svref ,slots ,index)) (defmacro wme-slot (wme index) "Given a wme and a slot index, return the slot value." `(slots-slot (wme-slots ,wme) ,index)) (defmacro wmep (wme) "Determines if argument is a wme." `(wme-p ,wme)) ;;; Slot Descriptions as lists to gain on search time (assoc) (defmacro make-slot (&key name index default) "A slot is a list of its name, index, and default value." `(list ,name ,index ,default)) (defmacro slot-name (slot) "Given a slot description, return its name." `(first ,slot)) (defmacro slot-index (slot) "Given a slot description, return its index." `(second ,slot)) (defmacro slot-default (slot) "Given a slot description, return its default." `(third ,slot)) (defmacro get-slot (name slots) "Get description of slot name from list of slots." `(assoc ,name ,slots :test #'eq)) (defmacro get-safe-slot (name type &optional context) "Get slot name from type, or print an error if non-existant. Cache name." `(or (get-slot ,name (wme-type-slots ,type)) (signal-output *command-trace* "SLOT ~S OF TYPE ~S IS UNDEFINED IN ~A." ,name ,type ,context))) (defmacro get-slot-value (wme slot) "Given a wme and a slot name, return its value if the slot exists." `(let ((index (get-slot ,slot (wme-type-slots (wme-type ,wme))))) (when index (wme-slot ,wme (slot-index index))))) (defmacro get-safe-slot-value (wme slot) "Given a wme and a slot name, return its value if the slot exists. Complain otherwise." `(let ((index (get-safe-slot ,slot (wme-type ,wme) ,wme))) (when index (wme-slot ,wme (slot-index index))))) ;;; Hash Table of wme names (defmacro get-wme (name) "Given a wme name, returns the wme itself." `(gethash ,name *hash-names*)) (defun get-wme-name (wme) "Get the name of wme, or return directly if not a wme." (if (wmep wme) (wme-name wme) wme)) (defmacro get-safe-wme (name &optional (warn t)) "Gets the wme corresponding to name. Prints an error message if undefined." `(if (wmep ,name) ,name (or (get-wme ,name) ,(if warn `(signal-warn "CHUNK ~S IS UNDEFINED." ,name) `(signal-output *command-trace* "CHUNK ~S IS UNDEFINED." ,name))))) (defun get-wme-or-constant (name) "Returns the wme corresponding to name, or the name if none. Detects nil. Now automatically defines non-wme symbols as of the default type wme." (cond ((null name) nil) ((eq name t) t) ;; t, like nil, is also given special status ((wmep name) name) ((get-wme name)) ((symbolp name) ; (signal-output *command-trace* "UNDEFINED CHUNK ~S IS BEING CREATED AS OF DEFAULT TYPE CHUNK." ; name) (create-wme name (cdar *declarative-memory*))) (t name))) (defmacro remove-name (name) "Given a wme name, remove its entry from the hash table." `(remhash ,name *hash-names*)) ;;; IA values as structures of 3 elements, indexed by wme in an a-list (defun print-ia (ia stream depth) "Print an ia as just its sji." (declare (ignore depth)) (format stream "~6,3F" (ia-sji ia))) (defstruct (ia (:print-function print-ia)) "An ia-link to wme i is indexed by the wme j and composed of its count (the number of times it appears in wme i), the current Sji value, the Rji* prior, and the F(Ni&Cj) statistics." (count 1) (sji 0.0) (rji* 1.0) (fnicj 0.0)) (defmacro default-rji* (wmej) "The default value of Rji* for that wme, i.e. m/n." `(/ *wme-number* (first (wme-fan ,wmej)))) (defmacro count-rji* (ia value) "Computes the value of Rji* of ia given the default value." `(if (<= (ia-count ,ia) 1) ,value (* ,value (ia-count ,ia)))) (defmacro rji-sji (rji) "Sji in terms of rji, i.e. log of rji." `(log ,rji)) (defmacro count-sji (ia value) "Computes Sji of ia from the default value." `(if (<= (ia-count ,ia) 1) ,value (+ ,value (log (coerce (ia-count ,ia) *read-default-float-format*))))) (defmacro learn-rji (ia wmej wmei) "Computes rji for ia between wmej and wmei based on ROM Equation 4.5." `(/ (+ (* *associative-learning* (ia-rji* ,ia)) (if (= (wme-needed ,wmei) 0.0) ; Eji defaults to 1 so Rji defaults to 0 (wme-contexts ,wmej) (* (ia-fnicj ,ia) (/ (- *cycle* (wme-creation-cycle ,wmei)) (wme-needed ,wmei))))) (+ *associative-learning* (wme-contexts ,wmej)))) (defmacro compute-ia (wmej ia wmei) "Initializes the IA connection from wmej to wmei." `(let ((rji* (count-rji* ,ia (default-rji* ,wmej)))) (setf (ia-rji* ,ia) rji*) (setf (ia-sji ,ia) (rji-sji (if *associative-learning* (learn-rji ,ia ,wmej ,wmei) rji*))))) (defmacro create-ia (wmej wmei &key (count 1) (sji 0.0) (rji* 1.0)) "Creates an IA connection between wmej and wmei. Count indicates whether the wmes are connected or not (learned)." `(let ((ia (make-ia :count ,count :sji ,sji :rji* ,rji*))) (push (cons ,wmej ia) (wme-ias ,wmei)) ia)) (defmacro ia-value (ia wmej wmei) "Compute new IA if necessary, and return the ia value." `(if (and *associative-learning* (< (wme-spread-stamp ,wmei) *spread-stamp*)) (setf (ia-sji ,ia) (rji-sji (learn-rji ,ia ,wmej ,wmei))) (ia-sji ,ia))) (defmacro delete-ia (wmej wmei) "Deletes the ia-link between two wmes." `(setf (wme-ias ,wmei) (delete ,wmej (wme-ias ,wmei) :test #'eq :key #'car :count 1))) (defmacro get-ia (wmej wmei) "Retrieves the ia-link between two wmes." `(cdr (assoc ,wmej (wme-ias ,wmei) :test #'eq))) (defmacro get-safe-ia (wmej wmei) "Gets the ia between wmej and wmei, or prints an error message if none." `(or (get-ia ,wmej ,wmei) (signal-warn "IA BETWEEN ~S AND ~S IS UNDEFINED." ,wmej ,wmei))) (defmacro get-make-ia (wmej wmei) "Gets the ia between wmej and wmei, or adds it if none." `(or (get-ia ,wmej ,wmei) (create-ia ,wmej ,wmei :count 0))) (defmacro remove-connection (wmej wmei &key (compute-ia t)) "Remove the connection between two wmes." `(let ((ia (get-safe-ia ,wmej ,wmei))) (decf (first (wme-fan ,wmej)) 1.0) (delete ,wmei (wme-fan ,wmej) :test #'eq :count 1) (decf (first (wme-slot-wmes ,wmei)) 1.0) (delete ,wmej (wme-slot-wmes ,wmei) :test #'eq :count 1) (if (= (decf (ia-count ia) 1) 0) (delete-ia ,wmej ,wmei) ,@(when compute-ia `((compute-ia ,wmej ia ,wmei)))))) (defmacro add-connection (wmej wmei &key (compute-ia t)) "Adds a connection between two wmes." `(let ((ia (get-ia ,wmej ,wmei))) (incf (first (wme-fan ,wmej)) 1.0) (push ,wmei (rest (wme-fan ,wmej))) (incf (first (wme-slot-wmes ,wmei)) 1.0) (push ,wmej (rest (wme-slot-wmes ,wmei))) (if ia (incf (ia-count ia) 1) (setf ia (create-ia ,wmej ,wmei))) ,@(when compute-ia `((compute-ia ,wmej ia ,wmei))))) (defmacro set-slot-value (wme slot-index value &key (compute-ia t)) "Sets wme slot index to value. Maintains the proper connections. Tests for analogy special slots." `(let ((old-value (wme-slot ,wme ,slot-index)) (new-value ,value)) (save-state-change :set-slot-value ,wme ,slot-index old-value) (when (wmep old-value) (remove-connection old-value ,wme :compute-ia ,compute-ia)) (when (wmep new-value) (add-connection new-value ,wme :compute-ia ,compute-ia)) (setf (wme-slot ,wme ,slot-index) new-value))) ;;; Similarities as a-lists (defmacro find-similarity (wmej wmei) "Returns the cons-cell holding the similarity between wmej and wmei, or nil." `(assoc ,wmei (wme-similarities ,wmej) :test #'eq)) (defmacro get-similarity (wmej wmei) "Retrieves the similarity between wmej and wmei, in that order, or 0.0." `(or (and *similarity-hook-fn* (funcall *similarity-hook-fn* ,wmej ,wmei)) (if (and (wmep ,wmej) (wmep ,wmei)) (or (cdr (find-similarity ,wmej ,wmei)) *max-dif*) (if (equal ,wmej ,wmei) *max-sim* *max-dif*)))) (defmacro set-similarity (wmej wmei similarity) "Sets the similarity between wmej and wmei to similarity." `(let ((existing (find-similarity ,wmej ,wmei))) (if existing (rplacd existing ,similarity) (push-last (cons ,wmei ,similarity) (wme-similarities ,wmej))))) ;;; Productions as structures (defun print-production (production stream depth) "Print a production as just its name, capitalized." (declare (ignore depth)) (format stream "~:(~A~)" (production-name production))) (defstruct (production (:print-function print-production) (:predicate productionp)) "Productions are represented as structures which hold the usual info, plus a list of instantiations for reuse." name goal-type text lhs rhs bindings retrievals initializations size instantiation extra-instantiation (creation-time *time*) (time-stamp (- *time* 1.0)) (references (cons 1.0 (when (and *strength-learning* (not *optimized-learning*)) (list *time*)))) (strength 0.0) success failure (chance 1.0) (effort *default-action-time*) (value 0.0) (p 1.0) (c *default-action-time*) pg-c (successes (cons 1.0 (when (numberp *parameters-learning*) (list *time*)))) (failures (list 0.0)) (efforts (cons *default-action-time* (when (numberp *parameters-learning*) (list *default-action-time*)))) documentation) ;;; Productions stored in a-list of name-structure pair for easy search (defmacro get-production (name &optional (productions '*procedural-memory*)) "Returns the production of a given name, if any, or nil." `(cdr (assoc ,name ,productions :test #'eq))) (defmacro get-safe-production (name &optional (productions '*procedural-memory*)) "Returns the production of a given name, or prints a warning." `(if (productionp ,name) ,name (or (get-production ,name ,productions) (signal-warn "PRODUCTION ~S IS UNDEFINED." ,name)))) ;;; Instantiations as arrays (defconstant *instantiation-slots* 3 "Specifies the number of common slots for each instantiation. Currently, the production, latency and gain.") (defmacro instantiation-production (instantiation) "Given an instantiation, returns the production." `(svref ,instantiation 0)) (defmacro instantiation-latency (instantiation) "Given an instantiation, returns its latency." `(svref ,instantiation 1)) (defmacro instantiation-gain (instantiation) "Given an instantiation, returns its expected gain." `(svref ,instantiation 2)) (defmacro instantiation-variable (instantiation index) "Given an instantiation and a variable index, returns the binding value." `(svref ,instantiation ,index)) (defmacro make-instantiation (production) "Create a new instantiation with the given production size." `(let ((instantiation (make-array (production-size ,production) :initial-element nil))) (setf (instantiation-production instantiation) ,production) instantiation)) (defmacro get-next-instantiation (production) "Picks the next instantiation from *extra-instantiation*, or generate a new one and add it to the list for production." `(or (pop *extra-instantiation*) (first (push (make-instantiation ,production) (production-extra-instantiation ,production))))) (defmacro copy-instantiation (instantiation) "Makes a copy of an instantiation for keeping in *previous-instantiations*." `(copy-seq ,instantiation)) ;;; Variable Bindings (defmacro make-variable-binding (name index &key (type-or-slot nil)) "Creates a binding for variable name of stack index. Type-or-slot indicates the wme type for actual retrievals and slot number for others. List to make it searchable by assoc. NOTE: Return arguments depend on this format to work correctly!" `(list ,name ,index ,type-or-slot)) (defmacro variable-name (binding) "Returns the variable name from binding." `(first ,binding)) (defmacro variable-index (binding) "Returns the variable stack index from binding." `(second ,binding)) (defmacro variable-type (binding) "Returns the variable type from binding." `(third ,binding)) (defmacro variable-slot (binding) "Returns the index of the slot from which this variable is bound." `(third ,binding)) (defmacro variable-slot-and-returns (binding) "Returns the index of the slot from which this variable is bound, and the following return indices." `(cddr ,binding)) (defmacro variable-returns (binding) "Returns the return bindings which are pushed at the end of the list." `(cdddr ,binding)) (defmacro get-variable-binding (variable bindings) "Retrieves the binding of variable from the a-list of variable bindings." `(assoc ,variable ,bindings :test #'eq)) (defmacro get-safe-variable-binding (variable bindings &optional context) "Retrieves the binding of variable from the a-list of variable bindings. Prints a warning if no such variable binding exists." `(or (get-variable-binding ,variable ,bindings) (signal-warn "VARIABLE ~S IS UNBOUND IN ~A." ,variable ,context))) (defmacro get-index-binding (index bindings) "Retrieves the variable binding index from the a-list of variable bindings." `(rassoc ,index ,bindings :test #'eq :key #'car)) (defmacro with-binding (binding existing variable bindings index &rest form) "Gets the binding of variable, or add a new one to bindings at index. Existing is set as to whether binding existed previously. Then form is evaluated with binding and existing bound." `(let ((,binding (get-variable-binding ,variable ,bindings)) (,existing t)) (unless ,binding (setf ,binding (make-variable-binding ,variable (incf ,index 1))) (push-last ,binding ,bindings) (setf ,existing nil)) ,@form)) ;;; Action Description (defstruct action "Creates an action consisting of a slot name and index, a value to be bound or tested, a dispatch keyword {:stack, :literal, or :eval} and a negation flag." name slot value dispatch negation) ;;; Goal Stack Frames (defun print-goal-frame (goal-frame stream depth) "Print a goal frame as just its focus." (declare (ignore depth)) (format stream "~A" (goal-frame-focus goal-frame))) (defstruct (goal-frame (:print-function print-goal-frame)) "Creates a goal stack frame containg the focus, and a description of which return values to pass back and how. Also keep the sources as existing when the chunk was created." focus return-values sources) ;;; Functional parameters (defun print-functional-parameter (parameter stream depth) (declare (ignore depth)) (format stream "~S" (functional-parameter-expression parameter))) (defstruct (functional-parameter (:print-function print-functional-parameter)) "Stores a functional production parameter as the original expression and the compiled function." expression function) ;;; Syntax Macros (defmacro first-char (string) "First character of the string." `(schar ,string 0)) (defmacro last-char (string) "Last character of the string." `(schar ,string (- (length ,string) 1))) (defmacro var> (string) "Determines if there is a > character at the end of the string." `(and (eq #\> (last-char ,string)) (> (length ,string) 1))) (defmacro !keyword! (string) "Determines if the first and last character of the string is a !." `(and (eq #\! (first-char ,string)) (eq #\! (last-char ,string)))) (defmacro =var (string) "Determines if the first character of the string is a =." `(eq #\= (first-char ,string))) (defmacro +var (string) "Determines if the first character of the string is a +." `(eq #\+ (first-char ,string))) (defmacro -var (string) "Determines if the first character of the string is a -." `(eq #\- (first-char ,string))) (defmacro delimiter (string) "Determines if string is either a var> or a !keyword!." `(or (var> ,string) (!keyword! ,string))) (defmacro symbol-name-test (symbol test) "Gets the name of symbol and apply test to it." `(and (symbolp ,symbol) (let ((name (symbol-name ,symbol))) (,test name)))) (defmacro retrievalp (symbol) "Determines whether symbol is of the form var>." `(symbol-name-test ,symbol var>)) (defmacro commandp (symbol) "Determines whether symbol is of the form !keyword!." `(symbol-name-test ,symbol !keyword!)) (defmacro delimiterp (symbol) "Determines whether symbol is of the form var> or !keyword!." `(symbol-name-test ,symbol delimiter)) (defmacro separatorp (symbol) "Determines if symbol is the separator ==>." `(eq '==> ,symbol)) (defmacro variablep (symbol) "Determines whether symbol is of the form =var." `(symbol-name-test ,symbol =var)) (defmacro actionp (symbol) "Determines whether symbol is of the form +var." `(symbol-name-test ,symbol +var)) (defmacro clearp (symbol) "Determines whether symbol is of the form -var." `(symbol-name-test ,symbol -var)) (defmacro position-or-last (list test) "Determines the index of the first element in list to satisfy test, or last." `(let ((index 0)) (dolist (symbol ,list index) (when (,test symbol) (return index)) (incf index 1)))) (defmacro next-delimiter (list) "Determines the index of the position of the next delimiter, the current one not included." `(position-or-last (rest ,list) delimiterp)) (defmacro test-modifier (symbol) "Determines if symbol is a test modifier such as negation or comparison." `(member ,symbol '(- < > <= >=) :test #'eq)) (defmacro next-slot (list) "Returns 1 for positive tests, 2 for negative or comparison tests." `(if (test-modifier (first ,list)) 2 1)) (defmacro var-var> (var) "Adds a final > to var." `(intern (concatenate 'string (coerce-string ,var) ">"))) (defmacro var>var (var) "Strips the final > off the var." `(let* ((name (symbol-name ,var)) (length (- (length name) 1)) (new-string (make-string length))) (dotimes (i length (intern new-string)) (setf (schar new-string i) (schar name i))))) (defmacro var=var (var) "Strips the initial = off a var." `(let* ((name (symbol-name ,var)) (length (- (length name) 1)) (new-string (make-string length))) (dotimes (i length (intern new-string)) (setf (schar new-string i) (schar name (+ i 1)))))) (defun coerce-string (expr) "Returns the string corresponding to expr. Tries to avoid using format." (when (wmep expr) (setf expr (wme-name expr))) (cond ((stringp expr) expr) ((symbolp expr) (symbol-name expr)) (t (format nil "~S" expr)))) ;;; ;;; UTILITIES ;;; (defmacro push-last (item place) `(setf ,place (nconc ,place (list ,item)))) (defun noise (s) "Approximates a sample from a normal distribution with mean zero and the given s-value (/ (sqrt (* 3.0 variance)) 3.1416)." ;; Need to test bound because of short-float lack of precision (let ((p (max 0.0001 (min (random 1.0) 0.9999)))) (* s (log (/ (- 1.0 p) p))))) (defmacro exp-log (base power) "Computes expt using exp and log to prevent getting out of short-float, if necessary." (if (typep (expt 1.0 1.0) *read-default-float-format*) `(expt ,base ,power) `(exp (* (log ,base) ,power)))) (defun quote-arguments (argument-list) "Return a new argument list with the non-constants quoted." (let ((new-list nil)) (dolist (argument argument-list new-list) (push-last (if (or (constantp argument) (variablep argument) (and (symbolp argument) (boundp argument))) argument (list 'quote argument)) new-list)))) ;;; ;;; MAIN FUNCTION DEFINITIONS ;;; ;;; Clearing, Resetting and Deleting (defmacro safe-gentemp (prefix) "gentemp a new symbol and save it for later removal." `(car (push (gentemp ,prefix) *used-ACT-R-symbols*))) (defun clear-used-symbols-fct () "Uninterns all of the symbols that have been safe-gentemp'ed since the last reset." (dolist (x *used-ACT-R-symbols*) (unintern x)) (setf *used-ACT-R-symbols* nil)) (defun init-types () "Initializes the init types" (chunk-type-fct '(chunk)) (chunk-type-fct '(error condition)) (chunk-type-fct '(dependency goal modified stack constraints actions generals specifics dont-cares differents)) (let ((type-t (make-wme-type))) (setf (wme-type-name type-t) t) (setf (wme-type-documentation type-t) "The default type for productions without goals.") (setf (wme-type-size type-t) 0) (push-last (cons t type-t) *declarative-memory*)) (add-dm-fct '((failure isa error condition failure)))) (defun clear-all-fct (&optional (save-model t)) "Clears everything." (clear-used-symbols-fct) (setf *compile-eval-calls* nil) (when *load-pathname* (setf *model* (when save-model (list *load-pathname*))) (when (eq save-model t) (load-model-list *load-pathname*))) (setf *time* 0.0) (setf *start-time* 0.0) (setf *default-action-time* 0.05) (setf *latency* 0.0) (setf *cycle* 0) (setf *spread-stamp* 0) (setf *wme-number* 0.0) (setf *declarative-memory* nil) (setf *hash-names* (make-hash-table :test #'eq :size 1000)) (init-types) (setf *procedural-memory* nil) (setf *goal-activation* 1.0) (setf *goal-sources* nil) (setf *wmfocus* nil) (setf *wmfocus-list* nil) (setf *goal-stack* nil) (setf *goal-depth* 1) (setf *g* 20.0) (setf *exp-gain-noise* nil) (setf *retrieval* nil) (setf *retrieval-scheduler* nil) (setf *visual-location* nil) (setf *aural-location* nil) (setf *previous-instantiations* nil) (setf *previous-instantiation* nil) (setf *instantiation* nil) (setf *extra-instantiation* nil) (setf *conflict-set* nil) (setf *sum-exp-act* 0.0) (setf *latency-fn* 'old-latency) (setf *latency-factor* 1.0) (setf *latency-exponent* 1.0) (setf *base-level-constant* 0.0) (setf *activation-sources* nil) (setf *activation-noise* nil) (setf *permanent-activation-noise* nil) (setf *mismatch-penalty* 1.0) (setf *retrieval-threshold* nil) (setf *partial-matching* nil) (setf *max-sim* 1.0) (setf *max-dif* -10.0) (setf *temperature* 1.0) (setf *blending* nil) (setf *break-productions* nil) (setf *failed-productions* nil) (setf *cost-penalty* 1.0) (setf *initial-experience* 10.0) (setf *threshold-time* 2.0) (setf *enable-production-learning* nil) (setf *reinforce-analogized-production* t) (setf *production-compilation-parameters* nil) (setf *enable-rational-analysis* nil) (setf *enable-randomness* nil) (setf *utility-threshold* 0.0) (setf *optimized-learning* t) (setf *base-level-learning* nil) (setf *associative-learning* nil) (setf *strength-learning* nil) (setf *parameters-learning* nil) (setf *command-trace* t) (setf *output-trace* t) (setf *cycle-trace* t) (setf *latency-trace* nil) (setf *partial-matching-trace* nil) (setf *blending-trace* nil) (setf *production-compilation-trace* t) (setf *activation-trace* nil) (setf *conflict-resolution-trace* nil) (setf *conflict-set-trace* nil) (setf *goal-trace* nil) (setf *dm-trace* nil) (setf *production-trace* nil) (setf *matches-trace* nil) (setf *exact-matching-trace* nil) (setf *verbose* t) (setf *abort-instantiation* t) (setf *parameter-sets* nil) (setf *similarity-hook-fn* nil) (setf *blending-hook-fn* 'blending-arithmetic-mean) (setf *conflict-set-hook-fn* nil) (setf *firing-hook-fn* nil) (setf *cycle-hook-fn* nil) (setf *web-hook-fn* nil) (setf *end-run-hook-fn* nil) (setf *step-fn* 'step-fct) (setf *stop* nil) (setf *save-state-changes* nil) (setf *pop-upon-failure* nil) (when *init-hook-fn* (funcall *init-hook-fn*))) (defun clear-dm-fct () "Clears all working memory elements." (dolist (wmetype *declarative-memory*) (setf (wme-type-wmes (cdr wmetype)) nil)) (clrhash *hash-names*) (setf *wme-number* 0.0)) (defun clear-productions-fct () "Clears all productions from memory." (dolist (wmetype *declarative-memory*) (setf (wme-type-productions (cdr wmetype)) nil)) (setf *procedural-memory* nil) (setf *break-productions* nil) (setf *failed-productions* nil)) (defun actr-time-fct (&optional inc) "Returns the current act-r time, or adds inc to it if specified." (if inc (incf *time* inc) *time*)) (defparameter *merge* nil) (defun delete-wme (wme &optional (identical nil)) "Deletes a given wme node from the type memory and the hash table. Zeroes all its slots, and only proceed when it is not referenced. If identical is specified, any remaining reference to wme will be replaced with identical. Also replace occurrences of wme in buffers, retrieval scheduler and instantiation variables." (dolist (parent (rest (wme-fan wme))) (unless (eq parent wme) (if identical ; replace references to wme in chunk slots with identical (dotimes (index (wme-type-size (wme-type parent))) (when (eq (wme-slot parent index) wme) (set-slot-value parent index identical))) (signal-output *command-trace* "CHUNK ~S IS STILL REFERENCED IN CHUNK ~S WHEN DELETED." wme parent)))) ;; Clear occurences of wme in buffers (dolist (buffer *buffer-keywords*) (setf buffer (cdr buffer)) (when (eq (symbol-value buffer) wme) (setf (symbol-value buffer) identical))) ;; same for retrieval scheduler but make sure it works whether pair or wme (when *retrieval-scheduler* (if (consp *retrieval-scheduler*) (when (eq (cdr *retrieval-scheduler*) wme) (rplacd *retrieval-scheduler* identical)) (when (eq *retrieval-scheduler* wme) (setf *retrieval-scheduler* wme)))) ;; Clear occurences of wme in instantiation variables (let ((instantiation *instantiation*)) (when instantiation (dotimes (variable (production-size (instantiation-production instantiation))) (when (eq (instantiation-variable instantiation variable) wme) (setf (instantiation-variable instantiation variable) identical))))) ;; When merging print a warning message (when identical (signal-output *cycle-trace* "Merging chunk ~S into chunk ~S" wme identical) ;; added for production compilation purposes (setf *merge* (list wme identical))) (let ((wmetype (wme-type wme))) (decf *wme-number* 1.0) (dolist (slot (wme-type-slots wmetype)) (set-slot-value wme (slot-index slot) nil)) (remove-name (wme-name wme)) (setf (wme-type-wmes wmetype) (delete wme (wme-type-wmes wmetype) :test #'eq :count 1)) (save-state-change :delete-wme wme))) (defun delete-chunk-fct (wmes) "Deletes wmes." (let ((structures nil)) (dolist (wme wmes structures) (setf wme (get-safe-wme wme)) (when wme (push-last (wme-name wme) structures) (delete-wme wme))))) (defun delete-production (production) "Removes a production from procedural memory and type memory." (let ((type (production-goal-type production))) (save-state-change :delete-production production) (setf (wme-type-productions type) (delete production (wme-type-productions type) :test #'eq :count 1)) (setf *procedural-memory* (delete (production-name production) *procedural-memory* :test #'eq :key #'car :count 1)))) (defun remove-wmetype-definition (definition) "Removes the structure definitions pertaining to a wme type definition." (dolist (subtype (rest (wme-type-subtypes definition))) (setf (wme-type-supertypes subtype) (delete definition (wme-type-supertypes subtype) :test #'eq :count 1))) (dolist (supertype (wme-type-supertypes definition)) (setf (wme-type-subtypes supertype) (delete definition (wme-type-subtypes supertype) :test #'eq :count 1))) (setf *declarative-memory* (delete definition *declarative-memory* :test #'eq :key #'cdr :count 1))) ;;; WME Type Creating and Printing (defun pprint-wmetype (wmetype) "Pretty prints a wmetype." (signal-output *command-trace* "~S" wmetype) (when (wme-type-documentation wmetype) (signal-output *command-trace* "~S" (wme-type-documentation wmetype))) (let ((super-type (second (wme-type-supertypes wmetype)))) (when super-type (signal-output *command-trace* " <- ~S" super-type))) (dolist (slot (wme-type-slots wmetype)) (signal-output *command-trace* " ~S" (slot-name slot)) (when (slot-default slot) (signal-output *command-trace* " <- ~S" (slot-default slot)))) (wme-type-name wmetype)) (defun print-wmetypes (&optional (wmetypes nil)) "Prints definitions for a list of types, possibly empty." (let ((*verbose* t) (*goal-depth* 1) (structures nil)) (if wmetypes (dolist (wmetype wmetypes) (setf wmetype (get-safe-type wmetype "CHUNK-TYPE")) (when wmetype (push-last (pprint-wmetype wmetype) structures))) (dolist (wmetype *declarative-memory*) (setf wmetype (cdr wmetype)) (push-last (pprint-wmetype wmetype) structures))) structures)) (defun chunk-type-fct (name-and-slots) "Defines a new wme type. Checks that the syntax is correct. Clears the previous definition if wme type is already defined." (if name-and-slots (let* ((name-description (car name-and-slots)) (name (if (consp name-description) (car name-description) name-description)) (super-type (if (consp name-description) (cdr name-description) nil)) (documentation (when (stringp (second name-and-slots)) (second name-and-slots))) (slots (if documentation (cddr name-and-slots) (cdr name-and-slots))) (definition nil) (slot-index 0)) ; check type hierarchy (when super-type (unless (null (cdr super-type)) (signal-warn "TOO MANY OPTIONS SPECIFIED FOR TYPE ~S: ~S. ALL BUT THE FIRST ARE IGNORED." name super-type)) (if (and (eq (caar super-type) :include) (null (cddar super-type))) (setf super-type (get-safe-type (cadar super-type) name)) (signal-warn "UNKNOWN OPTION ~S SPECIFIED FOR TYPE ~S." (car super-type) name))) ; check for redefinition (setf definition (get-type name)) (cond (definition (signal-warn "TYPE ~S IS BEING REDEFINED. RESET STRONGLY SUGGESTED." name) (remove-wmetype-definition definition)) (t (setf definition (make-wme-type)))) (setf (wme-type-name definition) name) (setf (wme-type-documentation definition) documentation) (setf (wme-type-supertypes definition) (when super-type (copy-list (wme-type-supertypes super-type)))) (setf (wme-type-size definition) (if super-type (wme-type-size super-type) 0)) (setf (wme-type-slots definition) (when super-type (copy-list (wme-type-slots super-type)))) (push-last (cons name definition) *declarative-memory*) (push definition (wme-type-subtypes definition)) (dolist (super-type (wme-type-supertypes definition)) (push-last definition (wme-type-subtypes super-type))) (push definition (wme-type-supertypes definition)) (setf slot-index (wme-type-size definition)) (dolist (slot slots) (cond ((or (atom slot) (and (listp slot) (= (length slot) 2))) (push-last (make-slot :name (if (atom slot) slot (first slot)) :index slot-index :default (if (atom slot) nil (get-wme-or-constant (second slot)))) (wme-type-slots definition)) (incf slot-index 1)) (t (signal-warn "UNKNOWN SLOT FORMAT ~S FOR TYPE ~S." slot name)))) (setf (wme-type-size definition) slot-index) name) (print-wmetypes))) (defun pprint-instantiation (&optional (instantiation *instantiation*) (trace *command-trace*) (format *production-trace*)) "Pretty-prints an instantiation." (let ((production (instantiation-production instantiation))) (when (eq trace 'short) (setf trace t)) (signal-output trace "~S ~8,3F ~8,3F" production (instantiation-latency instantiation) (instantiation-gain instantiation)) (if (eq format 'short) (dolist (binding (production-bindings production)) (signal-output trace " ~A: ~A" (variable-name binding) (if (integerp (variable-index binding)) (or (instantiation-variable instantiation (variable-index binding)) "VARIABLE STILL UNBOUND") "RETURN VARIABLE STILL UNDETERMINED"))) (let ((bindings nil) (text nil)) (dolist (binding (production-bindings production)) (when (integerp (variable-index binding)) (let ((value (instantiation-variable instantiation (variable-index binding)))) (when value (push (cons (variable-name binding) value) bindings) (push (cons (var-var> (variable-name binding)) (var-var> value)) bindings))))) (setf text (sublis bindings (production-text production))) (pprint-production production trace text))))) (defun pprint-instantiations (&optional (instantiations *conflict-set*) (trace *command-trace*) (format *production-trace*)) "Pretty-prints instantiations on trace." (let ((index 0)) (when (eq trace 'short) (setf trace t)) (dolist (instantiation instantiations) (incf index 1) (signal-output trace "Instantiation ~D:" index) (pprint-instantiation instantiation trace format)))) ;;; WME Creation (defun create-wme (name type &key (given nil) (compute-ia t)) "Creates a wme of a given name and type, with the default slot values. If wme is given, then reuse it. If not, then initialize the structure. Install wme in the type memory and the hash table." (let ((wme (or given (make-wme))) (rji* 1.0) (sji 0.0)) (incf *wme-number* 1.0) (setf (wme-name wme) name) (setf (wme-type wme) type) (setf (wme-slots wme) (make-array (wme-type-size type) :initial-element nil)) (unless given (when *permanent-activation-noise* (setf (wme-permanent-noise wme) (noise *permanent-activation-noise*))) (setf (wme-fan wme) (list 1.0 wme)) (when compute-ia (setf rji* (default-rji* wme)) (setf sji (rji-sji rji*))) (create-ia wme wme :count 1 :sji sji :rji* rji*) (setf (wme-similarities wme) (list (cons wme *max-sim*)))) (dolist (slot (wme-type-slots type)) (when (slot-default slot) (set-slot-value wme (slot-index slot) (slot-default slot) :compute-ia compute-ia))) (setf (get-wme name) wme) (push-last wme (wme-type-wmes type)) (save-state-change :create-wme wme) wme)) (defun new-name-fct (name) "Returns a unique symbol as generated by gentemp based on name, which can be either a string, a symbol, or else defaults to CHUNK." (cond ((stringp name) (safe-gentemp (string-upcase name))) ((symbolp name) (safe-gentemp (symbol-name name))) (t (signal-warn "ARGUMENT ~S TO NEW-NAME IS NOT A SYMBOL OR STRING." name) (safe-gentemp "CHUNK")))) (defun add-dm-fct (wmes &key (reset-ia t)) "Adds the wmes to working memory." ; First pass: create the wme structures (let ((structures nil) (names nil)) ;; copy once for more effective modification (setf wmes (copy-tree wmes)) (do ((rest-wmes wmes (rest rest-wmes))) ((null rest-wmes)) (let ((wme (first rest-wmes))) (when (and (eq (first wme) 'isa) (not (eq (second wme) 'isa))) (setf wme (cons (new-name-fct (second wme)) wme)) (rplaca rest-wmes wme)) (let* ((wme-name (first wme)) (slots (if (stringp (second wme)) (cddr wme) (cdr wme)))) ; check that the format is correct (cond ((eq (first slots) 'isa) (let ((wme-structure (get-wme wme-name)) (wmetype (get-safe-type (second slots) wme-name))) (when (and wme-structure wmetype) (signal-output *command-trace* "CHUNK ~S ALREADY EXISTS. DELETING IT FIRST." wme-name) (delete-wme wme-structure)) ; create the new wme and install it in declarative memory and the hash (if wmetype (setf wme-structure (create-wme wme-name wmetype :given wme-structure :compute-ia nil)) (setf wmes (delete wme wmes :test #'eq))))) (t (signal-warn "NO TYPE SPECIFIED FOR CHUNK ~S." wme-name) (setf wmes (delete wme wmes :test #'eq))))))) ; Second pass: fill the slots with the cross-references (dolist (wme wmes) (let* ((wme-name (first wme)) (wme-structure (get-wme wme-name)) (wmetype (wme-type wme-structure)) (documentation (when (stringp (second wme)) (second wme))) (all-slots (if documentation (cddddr wme) (cdddr wme))) (slot nil)) (push-last wme-structure structures) (push-last wme-name names) (when documentation (setf (wme-documentation wme-structure) documentation)) (do ((slots all-slots (cddr slots))) ((null slots)) (setf slot (get-safe-slot (first slots) wmetype wme-name)) (when slot (set-slot-value wme-structure (slot-index slot) (get-wme-or-constant (second slots)) :compute-ia nil))))) ; Reset all IAs, or just those for the wmes (if reset-ia (reset-ia-fct) (dolist (structure structures) (reset-ia-wme structure))) names)) (defun set-dm-fct (wmes) "Same as addwm-fct." (add-dm-fct wmes)) (defun copy-chunks (wmes) "Makes a clean copy of wmes and return the copies." (let ((copies nil) (copy nil)) (dolist (wme wmes copies) (setf wme (get-safe-wme wme)) (when wme (setf copy (create-wme (safe-gentemp (symbol-name (wme-name wme))) (wme-type wme))) (dolist (slot (wme-type-slots (wme-type copy))) (set-slot-value copy (slot-index slot) (get-slot-value wme (slot-name slot)))) (push-last copy copies))))) (defun copy-chunk-fct (wmes) "Makes a clean copy of wmes and return the names." (get-name-fct (copy-chunks wmes))) ;;; WME Activation (defun reset-ia-wme (wmei) "Resets all the ia values leading to wmei." (let ((wmej nil) (ia nil)) (dolist (wme-ia (wme-ias wmei)) (setf wmej (car wme-ia)) (setf ia (cdr wme-ia)) (compute-ia wmej ia wmei)))) (defun reset-ia-fct () "Resets all the ia values, preserving user-specified and learned values. Make sure that spreading activation is recomputed." (let ((rji* 0.0) (sji 0.0) (ia nil)) (for-all-wmes wmej (setf rji* (default-rji* wmej)) (setf sji (rji-sji rji*)) (dolist (wmei (rest (wme-fan wmej))) (setf ia (get-ia wmej wmei)) (setf (ia-rji* ia) (count-rji* ia rji*)) (setf (ia-sji ia) (count-sji ia sji)))) (incf *spread-stamp* 1) nil)) (defun ia-fct (wmej wmei) "Returns the ia value between wmej and wmei." (let* ((*verbose* t) (*goal-depth* 1) (ia nil)) (setf wmej (get-safe-wme wmej)) (setf wmei (get-safe-wme wmei)) (when (and wmej wmei) (setf ia (get-ia wmej wmei))) (setf ia (if ia (ia-value ia wmej wmei) 0.0)) (signal-output *command-trace* "~6,3F" ia) ia)) (defun update-activation-spread (&key (focus *wmfocus*)) "Updates the activation sources to be the slot values of the focus wme." (incf *spread-stamp* 1) (dolist (source *activation-sources*) (setf (wme-source source) nil)) (setf *activation-sources* nil) (when focus (let ((level (first (wme-slot-wmes focus)))) (when (> level 0.0) (setf level (/ *goal-activation* level)) (dolist (source (rest (wme-slot-wmes focus))) (cond ((wme-source source) (incf (wme-source source) level)) (t (setf (wme-source source) level) (push source *activation-sources*)))))))) (defun compute-spreading-activation (wmei) "Updates the spreading activation of wmei. Maintains activation." (let ((wmej nil) (ia nil) (spread 0.0)) (decf (wme-activation wmei) (wme-source-spread wmei)) (setf (wme-source-spread wmei) 0.0) (dolist (wme-ia (wme-ias wmei)) (setf wmej (car wme-ia)) (when (wme-source wmej) (setf ia (cdr wme-ia)) (setf ia (ia-value ia wmej wmei)) (setf spread (* (wme-source wmej) ia)) (signal-output *activation-trace* " Spreading activation ~6,3F from source ~S level ~6,3F times IA ~6,3F" spread wmej (wme-source wmej) ia) (incf (wme-source-spread wmei) spread))) (setf (wme-spread-stamp wmei) *spread-stamp*) (incf (wme-activation wmei) (wme-source-spread wmei)) (wme-source-spread wmei))) (defun compute-references (n references creation-time minus-decay) "Computes generalized decay formula from number and list of references, creation time and minus the decay rate." (let ((value 0.0) (last-reference 0.0)) (when references (dolist (reference references) (incf value (exp-log (max *default-action-time* (- *time* reference)) minus-decay)) (setf last-reference reference))) (when *optimized-learning* (let ((denominator (+ 1.0 minus-decay))) (if (numberp *optimized-learning*) (when (> n *optimized-learning*) (incf value (/ (* (- n *optimized-learning*) (- (exp-log (- *time* creation-time) denominator) (exp-log (- *time* last-reference) denominator))) (* (max *default-action-time* (- last-reference creation-time)) denominator)))) (setf value (/ (* n (exp-log (max *default-action-time* (- *time* creation-time)) minus-decay)) denominator))))) (log value))) (defun compute-base-level-activation (wme) "Updates the base level activation of wme when base level learning." (setf (wme-base-level wme) (+ *base-level-constant* (compute-references (car (wme-references wme)) (cdr (wme-references wme)) (wme-creation-time wme) (- *base-level-learning*)))) (signal-output *activation-trace* " Computing a base level of ~6,3F from ~D references from creation time ~6,3F" (wme-base-level wme) (round (first (wme-references wme))) (wme-creation-time wme)) (wme-base-level wme)) (defun activation (wme) "Returns the activation of wme. May involve recomputing the spreading activation, the base level and adding some noise." (declare (inline compute-spreading-activation compute-base-level-activation noise)) (when (and *enable-rational-analysis* (< (wme-spread-stamp wme) *spread-stamp*)) (compute-spreading-activation wme)) (when (and (or *base-level-learning* *activation-noise*) (< (wme-time-stamp wme) *time*)) (when *base-level-learning* (compute-base-level-activation wme)) (setf (wme-activation wme) (+ (wme-permanent-noise wme) (wme-base-level wme) (wme-source-spread wme))) (when *activation-noise* (let ((noise (noise *activation-noise*))) (signal-output *activation-trace* " Adding noise ~6,3F" noise) (incf (wme-activation wme) noise))) (setf (wme-time-stamp wme) *time*)) (wme-activation wme)) (defun update-activation-fct () "Updates the activation of all wmes by recomputing it. Also updates the IAs when associative learning is on." (let ((*verbose* t) (*goal-depth* 1)) (incf *spread-stamp* 1) (for-all-wmes wme (when *associative-learning* (dolist (wme-ia (wme-ias wme)) (ia-value (cdr wme-ia) (car wme-ia) wme))) (activation wme)))) ;;; WME Parameters and Printing (defun even-references (start end n &optional (m n)) "Distributes m references evenly along n intervals between start and end." (when (plusp n) (let ((decrement (/ (- end start) n)) (time end) (times nil)) (dotimes (i (round m) times) (decf time decrement) (push-last time times))))) (defun adapt-references (references creation-time &optional (optimized-learning *optimized-learning*)) "If optimized learning is off, then erase all reference times. If on, then generate equidistant references since creation time. Generalize to a fixed number of references." (cond ((eq optimized-learning t) (rplacd references nil)) ((null optimized-learning) (nconc references (even-references creation-time (if (null (rest references)) *time* (first (last references))) (- (first references) (length (rest references)))))) ((<= optimized-learning (length (rest references))) (rplacd (nthcdr optimized-learning references) nil)) (t (nconc references (even-references creation-time (if (null (rest references)) *time* (first (last references))) (- (first references) (length (rest references))) (- (min optimized-learning (first references)) (length (rest references)))))))) (defun get-base-level-fct (wmes) "Returns the base level of wmes." (let* ((*verbose* t) (*goal-depth* 1) (baselevel 0.0) (baselevels nil)) (dolist (wme wmes baselevels) (setf wme (get-safe-wme wme)) (when wme (setf baselevel (if (and *base-level-learning* (< (wme-time-stamp wme) *time*)) (compute-base-level-activation wme) (wme-base-level wme))) (signal-output *command-trace* "~6,3F" baselevel) (push-last baselevel baselevels))))) (defun set-base-level (wme setting) "Sets the base level of wme. setting is either the base level or the references, followed perhaps by the creation time. Increments activation." (let ((old-base-level (wme-base-level wme))) (when (second setting) (setf (wme-creation-time wme) (coerce (second setting) *read-default-float-format*))) (cond (*base-level-learning* (setf (wme-references wme) (list (coerce (first setting) *read-default-float-format*))) (unless *optimized-learning* (adapt-references (wme-references wme) (wme-creation-time wme))) (compute-base-level-activation wme)) (t (setf (wme-base-level wme) (coerce (first setting) *read-default-float-format*)))) (incf (wme-activation wme) (- (wme-base-level wme) old-base-level)) (wme-base-level wme))) (defun set-all-base-levels-fct (references &optional (creation-time nil)) "Sets all individual base levels to these references and creation-time." (let ((baselevel nil) (*activation-trace* nil)) (for-all-wmes wme (setf baselevel (set-base-level wme (list references creation-time)))) baselevel)) (defun set-base-levels-fct (settings) "Sets individual base levels. settings specify either the base level, if base level learning is off, or the references and perhaps the creation time." (let ((wme-or-type nil) (baselevels nil) (*activation-trace* nil)) (dolist (setting settings baselevels) (setf wme-or-type (first setting)) (unless (or (wmep wme-or-type) (wme-type-p wme-or-type)) (setf wme-or-type (get-type wme-or-type)) (unless wme-or-type (setf wme-or-type (get-safe-wme (first setting))))) (if (wmep wme-or-type) (push-last (set-base-level wme-or-type (rest setting)) baselevels) (when (wme-type-p wme-or-type) (dolist (wme (wme-type-wmes wme-or-type)) (push-last (set-base-level wme (rest setting)) baselevels))))))) (defun set-general-base-levels-fct (settings) "Sets individual base levels. settings specify either the base level, if base level learning is off, or the references and perhaps the creation time. Same as set-base-levels-fct." (set-base-levels-fct settings)) (defun set-ia-value (wmej wmei sji) "Sets ia value from wmej to wmei to sji. When wmej is a source, recompute spreading activation for wmei." (let ((ia (get-make-ia wmej wmei))) (when (wme-source wmej) (decf (wme-spread-stamp wmei) 1)) (setf (ia-rji* ia) (exp sji)) (setf (ia-sji ia) sji) sji)) (defun add-ia-fct (settings) "Sets individual ias. settings specify Sji as (wmej wmei sji)." (let ((wmej nil) (wmei nil) (ias nil)) (dolist (setting settings ias) (setf wmej (get-safe-wme (first setting))) (setf wmei (get-safe-wme (second setting))) (when (and wmej wmei) (push-last (set-ia-value wmej wmei (third setting)) ias))))) (defun set-ia-fct (settings) "Sets individual ias. settings specify Sji as (wmej wmei sji). Same as add-ia-fct." (add-ia-fct settings)) (defun activation-sources-fct () "Displays and returns activation sources." (let* ((*verbose* t) (*goal-depth* 1) (sources nil)) (dolist (source *activation-sources* sources) (push-last (wme-name source) sources) (signal-output *command-trace* "~S: ~6,3F" source (wme-source source))))) (defun chunk-slot-value-fct (wme slot) "Returns the slot value of wme." (setf wme (get-safe-wme wme)) (when wme (get-wme-name (get-safe-slot-value wme slot)))) (defun mod-chunk-fct (name slot-values) "Sets slots of wme name to values. Make sure that activation spreading is recomputed for all or just this wme." (let* ((wme (get-safe-wme name)) (type (when wme (wme-type wme))) (slot nil)) (when wme (do ((rest-slot-values slot-values (cddr rest-slot-values))) ((null rest-slot-values)) (setf slot (get-safe-slot (first rest-slot-values) type name)) (when slot (set-slot-value wme (slot-index slot) (get-wme-or-constant (second rest-slot-values))))) (if (eq wme *wmfocus*) (update-activation-spread) (decf (wme-spread-stamp wme) 1)) (wme-name wme)))) (defun mod-focus-fct (slot-values) "Sets slots of focus to values." (mod-chunk-fct *wmfocus* slot-values)) (defun get-name-fct (wmes-or-productions) "Given a list of wmes or productions, returns the list of names." (let ((*verbose* t) (*goal-depth* 1) (names nil)) (dolist (structure wmes-or-productions names) (cond ((wmep structure) (push-last (wme-name structure) names)) ((productionp structure) (push-last (production-name structure) names)) ((listp structure) (dolist (sublist structure) (push-last (get-name-fct sublist) names))) (t (signal-output *command-trace* "OBJECT ~S IN COMMAND NAME IS NEITHER A CHUNK NOR A PRODUCTION." structure)))))) (defun pprint-wme (wme) "Given a wme, prints the name, type, and slot values, and returns its name." (let ((wmetype (wme-type wme))) (signal-output *command-trace* "~A~S ~6,3F" (if (eq wme *wmfocus*) "**" "") wme (activation wme)) (when (wme-documentation wme) (signal-output *command-trace* "~S" (wme-documentation wme))) (signal-output *command-trace* " isa ~S" wmetype) (dolist (slot (wme-type-slots wmetype)) (let ((slot-value (wme-slot wme (slot-index slot)))) (signal-output *command-trace* " ~S ~S" (slot-name slot) slot-value))) (wme-name wme))) (defun dm-fct (wmes) "Pretty prints a list of wmes, or all of them if not specified." (let ((*verbose* t) (*goal-depth* 1) (structures nil)) (if wmes (dolist (wme wmes) (setf wme (get-safe-wme wme nil)) (when wme (push-last (pprint-wme wme) structures))) (for-all-wmes wme (push-last (pprint-wme wme) structures))) structures)) (defun sdm-fct (slot-values) "Prints wmes having slot values." (let ((*verbose* t) (*goal-depth* 1) (structures nil)) (dolist (type *declarative-memory* structures) (let ((slots (wme-type-slots (cdr type))) (slot nil) (index nil) (index-values nil)) (when (do ((rest-slot-values slot-values (cddr rest-slot-values))) ((null rest-slot-values) t) (setf slot (first rest-slot-values)) (cond ((eq slot 'isa) (unless (eq (second rest-slot-values) (car type)) (return nil))) (t (setf index (slot-index (get-slot slot slots))) (if index (push (cons index (or (get-wme (second rest-slot-values)) (second rest-slot-values))) index-values) (return nil))))) (dolist (wme (wme-type-wmes (cdr type))) (dolist (test index-values (push-last (pprint-wme wme) structures)) (unless (equal (cdr test) (wme-slot wme (car test))) (return nil))))))))) ;;; WME parameters setting and printing (defun wme-parameters-fct (wme &optional parameters) "Returns the value of the wme parameter(s), or print them all if none specified." (let ((*verbose* t) (*goal-depth* 1) (value nil) (values nil)) (setf wme (get-safe-wme wme)) (cond (wme (signal-output *command-trace* "Parameters for chunk ~S:" wme) ;;; Update activations!! (when *enable-rational-analysis* (when *associative-learning* (dolist (wme-ia (wme-ias wme)) (ia-value (cdr wme-ia) (car wme-ia) wme))) (activation wme)) (cond (parameters (dolist (parameter parameters) (setf value (case parameter (:name (wme-name wme)) (:activation (wme-activation wme)) (:source (wme-source wme)) (:base-level (wme-base-level wme)) (:creation-time (wme-creation-time wme)) (:references (wme-references wme)) (:source-spread (wme-source-spread wme)) (:ias (wme-ias wme)) (:creation-cycle (wme-creation-cycle wme)) (:needed (wme-needed wme)) (:contexts (wme-contexts wme)) (:permanent-noise (wme-permanent-noise wme)) (:similarities (wme-similarities wme)) (t (signal-warn "NO PARAMETER ~A DEFINED FOR CHUNK (TRY : IN FRONT)." parameter) :error))) (push-last value values) (signal-output *command-trace* "~S ~6,3F" parameter value)) (signal-output *command-trace* "") values) (t (when *enable-rational-analysis* (signal-output *command-trace* ":Activation ~6,3F~% :Source ~6,3F~% :Base-Level ~6,3F" (wme-activation wme) (wme-source wme) (wme-base-level wme)) (when *base-level-learning* (signal-output *command-trace* ":Creation-Time ~6,3F~% :References ~6,3F" (wme-creation-time wme) (wme-references wme))) (signal-output *command-trace* ":Source-Spread ~6,3F~% :IAs ~6,3F" (wme-source-spread wme) (wme-ias wme)) (when *associative-learning* (signal-output *command-trace* ":Creation-Cycle ~6,3F~% :Needed ~6,3F~% :Contexts ~6,3F" (wme-creation-cycle wme) (wme-needed wme) (wme-contexts wme))) (when *permanent-activation-noise* (signal-output *command-trace* ":Permanent-Noise ~6,#F" (wme-permanent-noise wme))) (when *partial-matching* (signal-output *command-trace* ":Similarities ~6,3F" (wme-similarities wme)))) (signal-output *command-trace* "") wme))) (t :error)))) (defun set-wme-parameters-fct (wme parameters) "Sets the parameters of wme." (let ((values nil)) (setf wme (get-safe-wme wme)) (if wme (loop (unless parameters (return values)) (let* ((parameter (pop parameters)) (value (pop parameters))) (when (and (listp value) (eq (first value) 'quote)) (setf value (second value))) ;; for compatibility with evaluating versions (push-last (case parameter (:name (signal-warn "CHUNK NAME CANNOT BE SET.") :error) (:activation (signal-warn "CHUNK ACTIVATION CANNOT BE SET DIRECTLY: SET BASE-LEVEL AND/OR SOURCE INSTEAD.") :error) (:base-level (cond (*base-level-learning* (signal-warn "CHUNK BASE-LEVEL CANNOT BE SET DIRECTLY WHEN BASE LEVEL LEARNING IS ENABLED: SET CREATION-TIME AND/OR REFERENCES INSTEAD.") :error) ((numberp value) (incf (wme-activation wme) (- value (wme-base-level wme))) (setf (wme-base-level wme) value)) (t (signal-warn "CHUNK BASE-LEVEL MUST BE SET TO A NUMBER.") :error))) (:source (cond ((or (numberp value) (null value)) (incf *spread-stamp* 1) (setf (wme-source wme) value)) (t (signal-warn "CHUNK SOURCE MUST BE SET TO NIL OR A NUMBER.") :error))) (:creation-time (cond ((numberp value) (decf (wme-time-stamp wme) 1.0) (setf (wme-creation-time wme) value)) (t (signal-warn "CHUNK CREATION-TIME MUST BE SET TO A NUMBER.") :error))) (:references (cond ((listp value) (decf (wme-time-stamp wme) 1.0) (setf (wme-references wme) (cons (length value) (unless *optimized-learning* (copy-list value)))) (adapt-references (wme-references wme) (wme-creation-time wme)) (wme-references wme)) ((numberp value) (decf (wme-time-stamp wme) 1.0) (setf (first (wme-references wme)) value) (adapt-references (wme-references wme) (wme-creation-time wme)) (wme-references wme)) (t (signal-warn "CHUNK REFERENCES MUST BE SET TO A NUMBER OR A LIST.") :error))) (:source-spread (signal-warn "CHUNK SOURCE-SPREAD CANNOT BE SET DIRECTLY: SET SOURCE AND/OR IAS INSTEAD.") :error) (:ias (cond ((listp value) (incf *spread-stamp* 1) (dolist (ia-pair value) (let ((wmei (get-safe-wme (car ia-pair))) (ia (if (numberp (cdr ia-pair)) (cdr ia-pair) (cadr ia-pair)))) (if (numberp ia) (when wmei (set-ia-value wmei wme ia)) (signal-warn "CHUNK IA VALUE ~S IS NOT A NUMBER." ia)))) (wme-ias wme)) (t (signal-warn "CHUNK IAS MUST BE SET TO A LIST OF CHUNK-NUMBER PAIRS.") :error))) (:needed (cond ((numberp value) (incf *spread-stamp* 1) (setf (wme-needed wme) value)) (t (signal-warn "CHUNK NEEDED MUST BE SET TO A NUMBER.") :error))) (:contexts (cond ((numberp value) (incf *spread-stamp* 1) (setf (wme-contexts wme) value)) (t (signal-warn "CHUNK CONTEXTS MUST BE SET TO A NUMBER.") :error))) (:creation-cycle (cond ((numberp value) (incf *spread-stamp* 1) (setf (wme-creation-cycle wme) value)) (t (signal-warn "CHUNK CREATION-CYCLE MUST BE SET TO A NUMBER.") :error))) (:permanent-noise (cond ((numberp value) (incf (wme-activation wme) (- value (wme-permanent-noise wme))) (setf (wme-permanent-noise wme) value)) (t (signal-warn "CHUNK PERMANENT-NOISE MUST BE SET TO A NUMBER.") :error))) (:similarities (cond ((listp value) (dolist (similarity-pair value) (let ((wmei (get-safe-wme (car similarity-pair))) (similarity (if (numberp (cdr similarity-pair)) (cdr similarity-pair) (cadr similarity-pair)))) (if (numberp similarity) (when wmei (set-similarity wme wmei similarity)) (signal-warn "CHUNK SIMILARITY VALUE ~S IS NOT A NUMBER." similarity)))) (wme-similarities wme)) (t (signal-warn "CHUNK SIMILARITIES MUST BE SET TO A LIST OF CHUNK-NUMBER PAIRS.") :error))) (t (signal-warn "NO PARAMETER ~A DEFINED FOR CHUNKS (TRY : IN FRONT)." parameter) :error)) values))) :error))) (defun sdp-fct (parameters) "Inspects and sets wme parameters." (let ((results nil)) (if (null parameters) ; print all parameters for all wmes (for-all-wmes wme (push-last (wme-parameters-fct wme) results)) (dolist (description (if (or (keywordp (first parameters)) (keywordp (second parameters)) (and (listp (first parameters)) (null (second parameters)) (not (keywordp (second (first parameters)))))) (list parameters) parameters)) (when (atom description) (setf description (list description))) (if (keywordp (first description)) (for-all-wmes wme (push-last (if (and (cdr description) (not (keywordp (second description)))) (set-wme-parameters-fct wme description) (wme-parameters-fct wme description)) results)) (dolist (wme (if (atom (first description)) (list (first description)) (first description))) (push-last (if (and (cddr description) (not (keywordp (third description)))) (set-wme-parameters-fct wme (rest description)) (wme-parameters-fct wme (rest description))) results))))) results)) ;;; Common similarity and blending functions. (defun linear-similarity (wmej wmei) "Implements the linear similarity scale scaled by *linear-similarity-scale*. Suitable for use as the value of *similarity-hook-fn*. Returns the similar defined as the difference divided by scale, or nil if both arguments are not numbers." (when (and (numberp wmej) (numberp wmei)) (/ (abs (- wmej wmei)) *linear-similarity-scale*))) (defun blending-arithmetic-mean (value-probs) "Averages the value-probabilities using arithmetic mean. Suitable for linear similarities between numbers." (let ((sum 0.0)) ;; Return the sum of all values multiplied by their probabilities (dolist (value-prob value-probs sum) (incf sum (* (car value-prob) (cdr value-prob)))))) (defun ratio-similarity (wmej wmei) "Implements the ratio similarity scale (no need for scaling). Suitable for use as the value of *similarity-hook-fn*. Returns the similar defined as the ratio of smallest to largest, or nil if both arguments are not numbers." (when (and (numberp wmej) (numberp wmei)) (let ((min (min (abs wmej) (abs wmei))) (max (max (abs wmej) (abs wmei)))) (if (zerop max) 1.0 ;; if max is 0 then wmej=wmei=0 (/ min max))))) (defun blending-geometric-mean (value-probs) "Averages the value-probabilities using geometric mean. Suitable for ratio similarities between numbers" (let ((sum 1.0)) ;; Return the product of all values to the exponent of their probabilities ;; This will not work for negative values, but the ratio scale really only ;; applies to positive numbers anyway. (dolist (value-prob value-probs sum) (setf sum (* sum (expt (car value-prob) (cdr value-prob))))))) (defun first-equation (best-value value-probs total-prob) "First activation equation (see ACT-R Workshop 99 slides)." (let ((activation 0.0)) (dolist (value-prob value-probs) (let ((temperature (or *temperature* (* (sqrt 2.0) *activation-noise*))) (match-score (cdr value-prob))) ;; recover chunk matching score from its probability (unless (zerop total-prob) (setf match-score (* match-score total-prob))) (setf match-score (* temperature (log match-score))) ;; apply the mistatch penalty between values (decf match-score (- 1.0 (or (get-similarity (car value-prob) best-value) 0.0))) ;; add the exponential of the new match scores (incf activation (exp match-score)))) (if (zerop activation) ;; just in case *retrieval-threshold* (log activation)))) (defun second-equation (best-value value-probs total-prob) "Second activation equation (see ACT-R Workshop 99 slides)." (let ((activation 0.0)) (dolist (value-prob value-probs) (let ((temperature (or *temperature* (* (sqrt 2.0) *activation-noise*))) (match-score (cdr value-prob))) ;; recover chunk matching score from its probability (unless (zerop total-prob) (setf match-score (* match-score total-prob))) (setf match-score (* temperature (log match-score))) ;; add the exponential of the new match scores weighted by similarity (incf activation (* (exp match-score) (or (get-similarity (car value-prob) best-value) 0.0))))) (if (zerop activation) ;; just in case *retrieval-threshold* (log activation)))) (defun third-equation (best-value value-probs total-prob) "Third activation equation (see ACT-R Workshop 99 slides)." (declare (ignore total-prob)) (let ((activation 0.0)) (dolist (value-prob value-probs) (incf activation (* (cdr value-prob) (or (get-similarity (car value-prob) best-value) 0.0)))) (if (>= activation 1.0) ;; perfect agreement on the same value ;; which is only possible if it is retrieval threshold *retrieval-threshold* (log (/ activation (- 1.0 activation)))))) ;;; Latencies (defun old-latency (activation) "Old latency equation: scaled negative exponential of chunk activation. The activation of other chunks and threshold is not used for scaling." (* *latency-factor* (exp (- (* *latency-exponent* (+ (if *instantiation* (strength (instantiation-production *instantiation*)) 0.0) activation)))))) (defun competitive-latency-bounded (activation) "Competitive latency equation: scaled the old latency (exponential of activation) by the sum of the exponential of other chunks activation passed as second argument. Includes the chunk itself and the threshold and thus will always be greater than the latency factor." (* *latency-factor* (expt (/ *sum-exp-act* (exp (/ activation *temperature*))) *latency-exponent*))) (defun competitive-latency (activation) "Competitive latency equation: scaled the old latency (exponential of activation) by the sum of the exponential of other chunks activation passed as second argument. Includes the threshold but not the chunk itself and thus can be arbitrarily small." (let ((chunk-itself (exp (/ activation *temperature*)))) (* *latency-factor* (expt (/ (- *sum-exp-act* chunk-itself) chunk-itself) *latency-exponent*)))) (defmacro activation-latency (activation) "Given the activation, returns the latency according *latency-fn*." `(funcall *latency-fn* ,activation)) ;;; Learning (defmacro build-functional-parameter (expression bindings) "Builds a functional parameter from the expression and variable bindings." `(make-functional-parameter :expression ,expression :function (sub-eval-vars ,expression ,bindings))) (defmacro get-functional-parameter (production-parameter) "Returns the value of parameter for production. Tests for functional values." `(if (functional-parameter-p ,production-parameter) (funcall (functional-parameter-function ,production-parameter)) ,production-parameter)) (defmacro production-action-probability (production) "The probability that the action of production executes successfully is either chance if specified or P." `(or (get-functional-parameter (production-chance ,production)) (production-p ,production))) (defmacro production-action-cost (production) "The action cost of a production is either the effort if specified or C." `(or (get-functional-parameter (production-effort ,production)) (production-c ,production))) (defun add-reference (references &optional (optimized-learning *optimized-learning*) (count 1.0) (reference *time*)) "Increments the reference count and pushes an extra reference if optimized learning is off. Works for both wme and production." (save-state-change :add-reference references) (incf (first references) count) (unless (eq optimized-learning t) (dotimes (i (round count)) (push reference (rest references)))) ;;; when keeping a fixed number of references and there are too many, lose the oldest (when (and (numberp optimized-learning) (> (first references) optimized-learning)) (nbutlast references))) (defun learn-matching (&optional (instantiation *instantiation*)) "Applies the learning equations 4.3, 4.5 and 4.6 to instantiation." (let ((production (instantiation-production instantiation)) (retrievals nil) (ia nil) (level 1.0)) (unless *blending* (when (or *base-level-learning* *associative-learning*) (dolist (retrieval (production-retrievals production)) (push-last (instantiation-variable instantiation retrieval) retrievals))) ;; For every WME matched on the LHS, accumulate the reference times ;; for use in Equation 4.3 (and Equation 4.5) (when *base-level-learning* (dolist (retrieval retrievals) (add-reference (wme-references retrieval)))) ;; For every context element (activation source) and every needed WME, ;; increment the number of times in context and needed, respectively, ;; and the number of coincidences for every pair for use in Equation 4.5. ;; DO NOT update the Sji since it changes every epoch (when *associative-learning* (dolist (retrieval retrievals) (incf (wme-needed retrieval) 1.0)) (dolist (wmej *activation-sources*) (incf (wme-contexts wmej) level) (dolist (retrieval retrievals) (setf ia (get-make-ia wmej retrieval)) (incf (ia-fnicj ia) level))))) ;; Adds the reference time for the selected production in Equation 4.6 (when *strength-learning* (add-reference (production-references production))))) (defun compute-probabilities (successes-list failures-list) "Computes probabilities by taking the ratio of successes to the sum of successes and failures. Computes those as decaying ratios if parameters learning is a number." (let ((successes 0.0) (failures 0.0)) (cond ((numberp *parameters-learning*) (let ((minus-d (- *parameters-learning*))) (dolist (success (rest successes-list)) (incf successes (exp-log (max *default-action-time* (- *time* success)) minus-d))) (dolist (failure (rest failures-list)) (incf failures (exp-log (max *default-action-time* (- *time* failure)) minus-d))))) (t (setf successes (first successes-list)) (setf failures (first failures-list)))) (/ successes (+ successes failures)))) (defun compute-p (p) "Computes the p parameter according to ACT Parameters Learning Equation." (setf (production-p p) (compute-probabilities (production-successes p) (production-failures p)))) (defun compute-costs (successes-list failures-list efforts-list) "Computes probabilities by taking the ratio of efforts to the sum of successes and failures. Computes those as decaying ratios if parameters learning is a number." (let ((successes 0.0) (failures 0.0) (efforts 0.0)) (cond ((numberp *parameters-learning*) (let ((minus-d (- *parameters-learning*)) (rest-successes (rest successes-list)) (rest-failures (rest failures-list)) (rest-efforts (rest efforts-list)) (decay 0.0)) (loop (when (or (and (null rest-successes) (null rest-failures)) (null rest-efforts)) (return)) (setf decay (if (and rest-successes (or (null rest-failures) (> (first rest-successes) (first rest-failures)))) (pop rest-successes) (pop rest-failures))) (setf decay (exp-log (max *default-action-time* (- *time* decay)) minus-d)) (incf successes decay) (incf efforts (* (pop rest-efforts) decay))))) (t (setf successes (first successes-list)) (setf failures (first failures-list)) (setf efforts (first efforts-list)))) (/ efforts (+ successes failures)))) (defun compute-c (p) "Computes the c parameter according to ACT Parameters Learning Equation." (setf (production-c p) (compute-costs (production-successes p) (production-failures p) (production-efforts p)))) (defmacro pg-c (production) "Computes PG-C of a production." `(setf (production-pg-c ,production) (- (* (production-p ,production) *g*) (production-c ,production)))) (defun all-pg-c () "Recomputes the pg-c of all productions." (dolist (production *procedural-memory*) (let ((p (cdr production))) (pg-c p)))) (defun recompute-production-parameters (production) "Recomputes all parameters for production." (when *parameters-learning* (compute-p production) (compute-c production)) (pg-c production)) (defun get-pg-c (production) "Returns the current pg-c of a production, which means the value of slot pg-c, unless *parameters-learning* is a number, in case the decaying value is computed." (when (numberp *parameters-learning*) (recompute-production-parameters production)) (production-pg-c production)) (defun learn-parameters (success failure &key (latency 0.0) (instantiation *instantiation*)) "Applies the parameters learning equations to instantiation. The success and/or failure of the goal has already been determined and is passed along with the latency of this instantiation and the instantiation itself (if it exists)." (let ((production (when instantiation (instantiation-production instantiation)))) ; removed for compatibility with production compilation version ; (when (and (not success) (not failure) instantiation) ;; see if goal changes: success ; (let ((goal-index (car (rassoc '*wmfocus* (production-initializations production))))) ; (when (and goal-index ;; not always mention of the goal in the production ; (not (equal *wmfocus* (instantiation-variable instantiation goal-index)))) ; (setf success t)))) ;;; success by default upon goal changes (cond ((and success failure) ;; clear the history (setf *previous-instantiations* nil)) ((or success failure) ;; register success or failure for all productions (when production (push (cons production (- *time* latency)) *previous-instantiations*)) (dolist (production-time *previous-instantiations*) (let* ((previous-production (car production-time)) (previous-time (cdr production-time)) (effort (- *time* previous-time))) (add-reference (if success (production-successes previous-production) (production-failures previous-production)) (not (numberp *parameters-learning*)) 1.0 previous-time) (add-reference (production-efforts previous-production) (not (numberp *parameters-learning*)) effort effort) (recompute-production-parameters previous-production))) (setf *previous-instantiations* nil)) (t ;; simply add the production (when production (push (cons production (- *time* latency)) *previous-instantiations*)))))) (defun strength (production) "Returns the production strength." (when (and *strength-learning* (< (production-time-stamp production) *time*)) (setf (production-strength production) (compute-references (car (production-references production)) (cdr (production-references production)) (production-creation-time production) (- *strength-learning*))) (setf (production-time-stamp production) *time*) (signal-output *activation-trace* "Computing a production strength of ~6,3F from ~D references from creation time ~6,3F" (production-strength production) (round (first (production-references production))) (production-creation-time production))) (production-strength production)) ;;; Production Parsing and Compiling (defmacro sublist (list index) "Makes the sublist from 0 to index the first element of list, with the sublist from index to the end being the rest. Destructively. Needs a special handling when index is zero." `(if (zerop ,index) (rplaca ,list (list (first ,list))) (let ((previous (nthcdr ,index ,list))) (rplaca ,list (cons (first ,list) (rest ,list))) (rplacd ,list (cdr previous)) (when previous (rplacd previous nil))))) (defmacro unflatten (expr index &key (subproc nil)) "Makes a list of sublists of expr by index. Every sublist is processed by subproc is present." `(let ((list ,expr)) (loop (unless list (return ,expr)) (sublist list (,index list)) ,@(when subproc (list `(,subproc (first list)))) (setf list (rest list))))) (defun sub-eval-vars (expr bindings) "Substitute stack reference for production variables in evaled expressions. Return a function to be funcalled rather than a form to be evaled, which for all but the simplest ones causes the compiler to be called every time, unless *compile-eval-calls* has been turned off." (let ((new-expr (list (copy-tree expr)))) (dolist (binding bindings (if *compile-eval-calls* (compile nil `(lambda () ,(first new-expr))) (eval `(function (lambda () ,(first new-expr)))))) (nsubst `(get-wme-name (svref *instantiation* ,(variable-index binding))) (variable-name binding) new-expr :test #'eq)))) (defmacro process-clause (clause) `(when (retrievalp (first ,clause)) (unflatten (rest ,clause) next-slot))) (defun structure-production (side) "Structures the definition of a production side." (unflatten side next-delimiter :subproc process-clause)) (defun parse-production (definition) "Parses a production on the basic format of name-lhs-==>-rhs, where lhs and rhs are lists of retrievals and commands. Each retrieval is assembled into the wme name, and a list of attribute-value pairs. Values can be negative or !eval!. Returns the name, the goal type, and the lhs and rhs." ; basic parsing; also copies the definition (let ((delimiter (position-or-last definition separatorp)) (documentation (when (stringp (second definition)) (second definition)))) (values (first definition) ; (if documentation (fifth definition) (fourth definition)) (structure-production (subseq definition (if documentation 2 1) delimiter)) (if (= delimiter (length definition)) (signal-warn "NO SEPARATOR ==> DEFINED IN PRODUCTION ~S" (first definition)) (structure-production (subseq definition (+ delimiter 1)))) documentation))) (defmacro set-variable-type (production type key pair) "Sets the type of variable key, or prints warning message if non-existent. First assigns type because usually given by pop. Also replaces type name with structure in text for better printing." `(let ((type-info ,pair)) (if (eq 'isa (first type-info)) (let ((type-structure (get-safe-type (second type-info) ,production))) (setf ,type type-structure) (when type-structure (setf (second type-info) type-structure))) (signal-warn "NO TYPE DEFINED FOR VARIABLE ~S IN ~A." ,key ,production)))) (defmacro compile-lhs-slots (bound-list unbound-list) "Compile slot-value pairs into a bound list and unbound list. Also update the text to replace constants with actual chunks for printing." `(dolist (slot-value clause) (setf negation (if (test-modifier (first slot-value)) (pop slot-value) nil)) (setf slot (first slot-value)) (setf slot (get-safe-slot slot type production)) (setf value (second slot-value)) (cond ((null slot)) ((and (listp value) (eq '!eval! (first value))) (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value (sub-eval-vars (second value) bindings) :dispatch :eval :negation negation) ,bound-list)) ((and (not (variablep value)) (not (assoc value *buffer-keywords*))) (let ((wme-or-constant (get-wme-or-constant value))) (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value wme-or-constant :dispatch :literal :negation negation) ,bound-list) (setf (second slot-value) wme-or-constant))) (t (with-binding value-binding bound value bindings index (cond (bound (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value (variable-index value-binding) :dispatch :stack :negation negation) ,bound-list)) (t (when negation (signal-warn "TEST MODIFIER ~S CANNOT BE USED TO SET ~S IN SLOT ~S IN PRODUCTION ~A: IGNORED." negation value slot production)) (setf (variable-slot value-binding) (slot-index slot)) (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value (variable-index value-binding)) ,unbound-list)))))))) (defun first-retrieval-index (lhs initializations) "Returns the index of the first retrieval in lhs." (let ((index 0) (form lhs)) (loop (unless form (return (length lhs))) (if (and (member (first form) '(direct-test-and-bind indirect-test-and-bind direct-test-and-bind-pm indirect-test-and-bind-pm) :test #'eq) (not (assoc (second form) initializations :test #'=))) (return index) (incf index 1)) (setf form (rest form))))) (defun compile-lhs (production lhs initializations bindings index) "Compiles the left-hand side of production. Add the instantiation-adding call." (let ((code nil) (form nil)) (dolist (clause lhs) (let ((key (pop clause)) (type nil) (tests nil) (binds nil) (slot nil) (value nil) (negation nil)) (setf form (cond ((retrievalp key) (setf key (var>var key)) (with-binding binding direct key bindings index (set-variable-type production type key (pop clause)) (when type (setf (variable-type binding) type) (compile-lhs-slots tests binds) (list (if (assoc (variable-index binding) initializations :test #'=) ;; Fixed buffers have their special test to avoid activation 'direct-test-and-bind-buffer (if *enable-rational-analysis* (if direct 'direct-test-and-bind-pm 'indirect-test-and-bind-pm) (if direct 'direct-test-and-bind 'indirect-test-and-bind))) (variable-index binding) type tests binds)))) ((eq '!eval! key) (list 'eval-test (sub-eval-vars (first clause) bindings))) ((eq '!bind! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list 'bind-lhs (variable-index binding) (sub-eval-vars (second clause) bindings)))) ((eq '!find-location! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list 'bind-find-location (variable-index binding) (sub-eval-vars (cons 'list (quote-arguments (rest clause))) bindings)))) (t (signal-warn "UNKNOWN COMMAND ~S IN PRODUCTION ~A." key production)))) (setf code (nconc code form)))) ;; FIX: split into goal and retrievals when ERA is enabled. (values (if *enable-rational-analysis* (let ((direct-length (first-retrieval-index code initializations))) (cons (nconc (subseq code 0 direct-length) (list 'add-instantiation-to-conflict-set)) (nconc (subseq code direct-length) (list 'not)))) (nconc code (list 'add-instantiation-to-conflict-set))) bindings index))) (defmacro compile-rhs-slots (bound-list unbound-list parent) "Compile slot-value pairs into a bound list and unbound (returns) list. Parent is used to know which goal this stack slot belongs to. Also update the text to replace constants with actual chunks for printing." `(dolist (slot-value clause) (setf negation (if (test-modifier (first slot-value)) (pop slot-value) nil)) (setf slot (first slot-value)) (setf slot (get-safe-slot slot type production)) (setf value (second slot-value)) (cond ((null slot)) ((and (listp value) (eq '!eval! (first value))) (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value (sub-eval-vars (second value) bindings) :dispatch :eval :negation negation) ,bound-list)) ((and (not (variablep value)) (not (get-variable-binding value bindings))) (let ((wme-or-constant (get-wme-or-constant value))) (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value wme-or-constant :dispatch :literal :negation negation) ,bound-list) (setf (second slot-value) wme-or-constant))) (t (with-binding value-binding bound value bindings index (cond (bound (if (numberp (variable-index value-binding)) ; if a normal variable, then push the action (push-last (make-action :name (slot-name slot) :slot (slot-index slot) :value (variable-index value-binding) :dispatch :stack :negation negation) ,bound-list) ; otherwise, add the reference to the return stack (let ((variable-references (get-variable-binding value ,unbound-list))) (push-last (cons (variable-name ,parent) (slot-index slot)) (variable-returns variable-references))))) (t (when negation (signal-warn "TEST MODIFIER ~S CANNOT BE USED TO SET ~S IN SLOT ~S IN PRODUCTION ~A: IGNORED." negation value slot production)) ; Return values do not need instantiation representation ; stack index is the parent's name (decf index) (setf (variable-index value-binding) ,parent) (setf (variable-slot value-binding) (slot-index slot)) (push-last value-binding ,unbound-list)))))))) (defmacro subgoal-returns (subgoal returns) "Select the returns from slots of subgoal." `(let ((selected nil)) (dolist (return ,returns selected) (when (eq (variable-name (variable-index return)) ,subgoal) (push (variable-slot-and-returns return) selected))))) (defun interpret-output-argument (argument bindings production) "Interprets a production variable as its stack index, and returns more complex arguments as functions." (if (variablep argument) (variable-index (get-safe-variable-binding argument bindings production)) (sub-eval-vars argument bindings))) (defun compile-output (clause bindings production) "Compiles an output clause for a production given bindings." (let ((output-string "~&~VT") (output-args nil)) (when (and (listp (first clause)) (null (rest clause))) (setf clause (first clause))) (cond ((stringp (first clause)) (setf output-string (concatenate 'string output-string (first clause))) (dolist (arg (rest clause)) (push-last (interpret-output-argument arg bindings production) output-args))) (t (dolist (arg clause) (cond ((or (variablep arg) (listp arg)) (setf output-string (concatenate 'string output-string "~S ")) (push-last (interpret-output-argument arg bindings production) output-args)) (t (setf output-string (concatenate 'string output-string (coerce-string arg) " "))))))) (cons output-string output-args))) (defmacro simulate-call (arguments) "Simulate production compilation by funcalling the function with the rest of the arguments." `(funcall (first ,arguments) (rest ,arguments))) (defun assign-retrieval (arguments) "Assigns to the variable *retrieval-scheduler* the result and latency of the retrieval. The variable *retrieval* is also reset to nil waiting for the result." (let ((retrieval (if (first arguments) (instantiation-variable *instantiation* (pop arguments)) *retrieval-scheduler*))) (signal-output *latency-trace* "Latency ~6,3F: ~A Retrieval" *latency* retrieval) (setf *retrieval* nil) (setf *retrieval-scheduler* (cons (+ *time* *latency*) retrieval)))) (defun handle-failure (arguments) "Calls whatever test-and-bind function is given as arguments. If it fails, clean the bindings and set them as well as retrieval to the chunk failure. Failure is defined as being of the default type error with itself as condition." (unless (simulate-call arguments) ; if matching fails set the bindings to failure (let ((failure (get-wme 'failure))) (unless failure ; if failure isn't yet defined then add it (add-dm-fct '((failure isa error condition failure)) :reset-ia nil) (setf failure (get-wme 'failure))) (dolist (action (fifth arguments)) (setf (instantiation-variable *instantiation* (action-value action)) failure)) (when (and (second arguments) (member (first arguments) '(indirect-test-and-bind indirect-test-and-bind-pm))) (setf (instantiation-variable *instantiation* (second arguments)) failure)) (signal-output *latency-trace* "Latency ~6,3F: Failure Retrieval" *latency*) (setf *retrieval* nil) (setf *retrieval-scheduler* (cons (+ *time* *latency*) failure))))) (defun assign-buffer (buffer-index) "Assigns value of instantiation variable index or function to buffer." (buffers-fct (list (first buffer-index) (let ((value (second buffer-index))) (when value (if (numberp value) (instantiation-variable *instantiation* value) (get-wme-or-constant (funcall value)))))))) (defun compile-rhs (production rhs bindings index) "Compiles the right-hand side of production." (let ((code nil) (returns nil) (goal-stack (list (variable-name (first bindings))))) (dolist (clause rhs) (let ((key (pop clause)) (type nil) (assigns nil) (slot nil) (value nil) (negation nil)) (setf code (nconc code (cond ((retrievalp key) (setf key (var>var key)) (when (actionp key) (setf key (var=var key))) ;; strip out the + (cond ((clearp key) ;; clearing buffers (setf key (var=var key)) ;; strip out the - (if (assoc key *buffer-keywords*) ;; clearing ACT-R buffers (list (list 'assign-buffer key nil)) (signal-warn "UNKNOWN BUFFER ~S CANNOT CLEAR." key))) ((and (assoc key *buffer-keywords*) (null (rest clause)) (null (rest (first clause))) (listp (first (first clause)))) ;; assignment to buffer of result of function call (list (list 'assign-buffer key (sub-eval-vars (first (first clause)) bindings)))) ((and (not (variablep key)) (assoc key *buffer-keywords*) (null (rest clause)) (null (rest (first clause)))) ;; direct assignment to buffers, including goal focus and direct retrieval (let ((binding (get-variable-binding (first (first clause)) bindings))) (cond ((eq key 'goal) ;; focus on goal (setf goal-stack (butlast goal-stack)) (push-last key goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns key returns)))) ((eq key 'retrieval) ;; direct retrieval (list (list 'handle-failure (if *enable-rational-analysis* 'direct-test-and-bind-pm 'direct-test-and-bind) (variable-index binding) type nil nil 'assign-retrieval (variable-index binding)))) (t ;; other buffer assignment (list (list 'assign-buffer key (variable-index binding))))))) (t (with-binding binding modify key bindings index (cond (modify (setf type (variable-type binding)) (if (and type (not (integerp type))) (when (eq 'isa (caar clause)) (set-variable-type production type key (pop clause)) (unless (or (eq type (variable-type binding)) (assoc key *buffer-keywords*)) (signal-warn "TYPE OF VARIABLE ~S IS BEING REDEFINED IN PRODUCTION ~A." key production))) (set-variable-type production type key (pop clause)))) (t (set-variable-type production type key (pop clause)))) (when type (cond ((or (eq 'retrieval key) (member (list '!retrieve! key) rhs :test #'equal)) (let ((tests nil) (binds nil)) (compile-lhs-slots tests binds) (list (list 'handle-failure (if *enable-rational-analysis* (if modify 'direct-test-and-bind-pm 'indirect-test-and-bind-pm) (if modify 'direct-test-and-bind 'indirect-test-and-bind)) nil type tests binds 'assign-retrieval nil)))) (t (compile-rhs-slots assigns returns binding) (cons (if modify (list 'modify-old-wme (variable-index binding) type assigns) (list 'create-new-wme (variable-index binding) (if (variablep key) (var=var key) key) type assigns)) (when (and (or (eq '=newgoal key) (eq 'newgoal key) (eq 'goal key)) (not (member (list '!push! key) rhs :test #'equal)) (not (member (list '!focus-on! key) rhs :test #'equal))) (setf goal-stack (butlast goal-stack)) (push-last key goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns key returns)))))))))))) ((eq '!retrieve! key) ;; do nothing since retrieval already done nil) ((eq '!push! key) (let* ((subgoal (first clause)) (binding (get-safe-variable-binding subgoal bindings production))) (when binding (push-last subgoal goal-stack) (list (list 'push-fct (variable-index binding) (subgoal-returns subgoal returns)))))) ((eq '!pop! key) ;;; FIX: pop from the end of the stack (setf goal-stack (butlast goal-stack)) (list (list 'pop-fct))) ((eq '!focus-on! key) (let* ((subgoal (first clause)) (binding (get-safe-variable-binding subgoal bindings production))) (when binding ;;; FIX: do not assume that there is something on the stack (setf goal-stack (butlast goal-stack)) (push-last subgoal goal-stack) (list (list 'focus-fct (variable-index binding) (subgoal-returns subgoal returns)))))) ((eq '!output! key) (list (list 'output (compile-output clause bindings production)))) ((eq '!eval! key) (list (list 'eval-side (sub-eval-vars (first clause) bindings)))) ((eq '!bind! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'bind-rhs (variable-index binding) (sub-eval-vars (second clause) bindings))))) ((eq '!move-attention! key) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'bind-move-attention (variable-index binding) (sub-eval-vars (cons 'list (quote-arguments (rest clause))) bindings))))) ((eq '!press-key! key) (list (list 'visual-action 'press-key (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!move-mouse! key) (list (list 'visual-action 'move-mouse (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!click-mouse! key) (list (list 'visual-action 'click-mouse (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!send-command! key) (list (list 'action-command (sub-eval-vars (cons 'list (quote-arguments clause)) bindings)))) ((eq '!delete! key) (let ((binding (get-safe-variable-binding (first clause) bindings production))) (when binding (list (list 'delete-wme-variable (variable-index binding)))))) ((or (eq '!copy! key) (eq '!copywme! key)) (with-binding binding bound (first clause) bindings index (when bound (signal-warn "VARIABLE ~S ALREADY BOUND IN PRODUCTION ~A." (first clause) production)) (list (list 'copy-chunk-variable (variable-index binding) (sub-eval-vars (cons 'list (rest clause)) bindings))))) ((eq '!stop! key) (list (list 'stop))) ((eq '!restart! key) (list (list 'restart-top-goal))) (t (signal-warn "UNKNOWN COMMAND ~S IN PRODUCTION ~A." key production))))))) ; remove the unknown commands ; (setf code (delete nil code :test #'eq)) ; translate wme variables into relative goal stack indices for value returns (dolist (return returns) (let ((from-position (position (variable-name (variable-index return)) goal-stack :test #'eq))) (if from-position (dolist (destination (variable-returns return)) (let ((to-position (position (first destination) goal-stack :test #'eq))) (if (and (numberp to-position) (> from-position to-position)) (rplaca destination (- from-position to-position 1)) (signal-warn "VARIABLE ~A CANNOT BE RETURNED BECAUSE CHUNK ~A IS NOT ON THE STACK BELOW SUBGOAL ~A IN PRODUCTION ~A." (variable-name return) (first destination) (variable-name (variable-index return)) production)))) (signal-warn "VARIABLE ~A CANNOT BE RETURNED BECAUSE CHUNK ~A IS NOT ON THE STACK IN PRODUCTION ~A." (variable-name return) (variable-name (variable-index return)) production)))) (values code bindings index))) (defun compile-buffers (production lhs bindings index) "Compiles the left-hand side references to pre-defined buffers such as goal and retrieval." (declare (ignore production)) (let ((initializations nil) (type t)) (dolist (clause lhs) (let ((key (first clause))) (when (retrievalp key) (setf key (var>var key)) (let ((buffer (assoc key *buffer-keywords*))) ;;; sets the type of the goal (when (or (eq key '=goal) (eq key 'goal)) (setf type (second (assoc 'isa (rest clause))))) (when buffer ;;; When the retrieval refers to a buffer, create a new binding (with-binding binding existing key bindings index (unless existing (push-last (cons index (rest buffer)) initializations)))))))) ; (signal-output *command-trace* "The special buffers in production ~S are ~S" ; production initializations) (values type initializations bindings index))) (defun compile-production (name lhs rhs &optional (documentation nil)) "Compile production name, type, lhs, rhs and optional documentation." (let ((production nil) (type nil) (initializations nil) (bindings nil) (retrievals nil) (lhs-code nil) (rhs-code nil) (stack-index (1- *instantiation-slots*))) (setf production (get-production name)) (multiple-value-setq (type initializations bindings stack-index) (compile-buffers name lhs bindings stack-index)) (setf type (get-safe-type type name)) (when type (cond (production (signal-warn "PRODUCTION ~S IS BEING REDEFINED." name) (delete-production production)) (t (setf production (make-production :name name)) (save-state-change :compile-production production))) (push-last (cons name production) *procedural-memory*) (setf (production-documentation production) documentation) (setf (production-goal-type production) type) (push-last production (wme-type-productions type)) (setf (production-text production) (cons lhs rhs)) ; (setf bindings (list (make-variable-binding (var>var (first (first lhs))) ; stack-index))) (setf (production-initializations production) initializations) (multiple-value-setq (lhs-code bindings stack-index) (compile-lhs name lhs initializations bindings stack-index)) (setf (production-lhs production) lhs-code) (multiple-value-setq (rhs-code bindings stack-index) (compile-rhs name rhs bindings stack-index)) (setf (production-rhs production) rhs-code) (setf (production-bindings production) bindings) (dolist (binding (rest bindings)) (when (typep (variable-type binding) 'wme-type) (push-last (variable-index binding) retrievals))) (setf (production-retrievals production) retrievals) (setf (production-size production) (+ stack-index 1)) (setf (production-instantiation production) (make-instantiation production)) ;; FIX: no more setting of extra-instantiation (pg-c production) production))) (defun p-fct (definition) "Defines a production by parsing then compiling it." (multiple-value-bind (name lhs rhs documentation) (parse-production definition) (let ((production (compile-production name lhs rhs documentation))) (when production (production-name production))))) (defun penable-fct (names) "Enables disabled productions." (let ((production nil)) (save-state-change :penable-fct names) (dolist (name names (mapcar #'car *failed-productions*)) (setf production (get-safe-production name *failed-productions*)) (when production (setf *failed-productions* (delete (production-name production) *failed-productions* :test #'eq :key #'car :count 1)) (push-last (cons (production-name production) production) *procedural-memory*) (push-last production (wme-type-productions (production-goal-type production))) (pg-c production))))) (defun pdisable-fct (names) "Disables productions." (let ((production nil)) (save-state-change :pdisable-fct names) (dolist (name names (mapcar #'car *failed-productions*)) (setf production (get-safe-production name)) (when production (delete-production production) (push-last (cons (production-name production) production) *failed-productions*))))) (defun pbreak-fct (names) "Sets break points for productions." (let ((production nil)) (dolist (name names *break-productions*) (setf production (get-safe-production name)) (when production (push-last (production-name production) *break-productions*))))) (defun punbreak-fct (names) "Removes break points for productions." (let ((production nil)) (dolist (name (or names *break-productions*) *break-productions*) (setf production (get-safe-production name)) (when production (setf *break-productions* (delete (production-name production) *break-productions* :test #'eq :count 1)))))) (defmacro print-side (side) "Prints one side of a production, i.e. a list of commands and wme retrievals." `(dolist (clause ,side) (cond ((retrievalp (first clause)) (signal-output trace " ~S" (first clause) ) (dolist (slot-value (rest clause)) (if (null (rest slot-value)) (signal-output trace " ~S" (first slot-value)) (signal-output trace " ~A ~S ~S" (if (test-modifier (first slot-value)) (pop slot-value) " ") (first slot-value) (second slot-value))))) (t (signal-output trace " ~{ ~S~}" clause))))) (defun pprint-production (production &optional (trace *command-trace*) (text (production-text production)) (documentation (production-documentation production))) "Pretty prints production. Pretty basic." (signal-output trace "(p ~S" production) (when documentation (signal-output trace " ~S" documentation)) (print-side (car text)) (signal-output trace "==>") (print-side (cdr text)) (signal-output trace ")") (production-name production)) (defun pp-fct (productions) "Prints the list of productions, or all the active ones if none specified." (let ((*verbose* t) (*goal-depth* 1) (structures nil)) (if productions (dolist (production productions) (setf production (or (get-production production) (get-safe-production production *failed-productions*))) (when production (push-last (pprint-production production) structures))) (dolist (production *procedural-memory*) (setf production (cdr production)) (push-last (pprint-production production) structures))) structures)) ;;; Production parameters setting and printing (defun displayable-production-parameters () "Returns the list of displayable production parameters given the current values of global parameters. Used by the interface." (nconc (list :chance :effort) (if *enable-rational-analysis* (nconc (list :strength) (when (or *strength-learning* (numberp *parameters-learning*)) (list :creation-time)) (when *strength-learning* (list :references)) (list :q :a :r :b :pg-c) (when *parameters-learning* (list :successes :failures :efforts :eventual-successes :eventual-failures :eventual-efforts :success :failure))) (list :value)))) (defun settable-production-parameters () "Returns the list of settable production parameters given the current values of global parameters. Used by the interface." (nconc (list :chance :effort) (if *enable-rational-analysis* (nconc (when (or *strength-learning* (numberp *parameters-learning*)) (list :creation-time)) (if *strength-learning* (list :references) (list :strength)) (if *parameters-learning* (list :successes :failures :efforts :eventual-successes :eventual-failures :eventual-efforts :success :failure) (list :q :a :r :b))) (list :value)))) (defun default-production-parameters () "Returns the production parameters with their default values. Used by the interface." (nconc (list (cons :chance 1.0) (cons :effort *default-action-time*)) (if *enable-rational-analysis* (nconc (when (or *strength-learning* (numberp *parameters-learning*)) (list (cons :creation-time *time*))) (if *strength-learning* (list (cons :references (cons 1.0 (when (and *strength-learning* (not *optimized-learning*)) (list *time*))))) (list (cons :strength 0.0))) (if *parameters-learning* (list (cons :successes (cons 1.0 (when (numberp *parameters-learning*) *time*))) (cons :eventual-successes (cons 1.0 (when (numberp *parameters-learning*) *time*))) (cons :failures (list 0.0)) (cons :eventual-failures (list 0.0)) (cons :efforts (cons *default-action-time* (when (numberp *parameters-learning*) *default-action-time*))) (cons :eventual-efforts (cons 1.0 (when (numberp *parameters-learning*) 1.0)))) (list (cons :q 1.0) (cons :a *default-action-time*) (cons :r 1.0) (cons :b 1.0)))) (list :value)))) (defun displayable-declarative-parameters () "Returns the list of displayable declarative parameters given the current values of global parameters. Used by the interface." (when *enable-rational-analysis* (nconc (list :activation :source :base-level) (when *base-level-learning* (list :creation-time :references)) (list :source-spread :ias) (when *associative-learning* (list :creation-cycle :needed :contexts)) (when *permanent-activation-noise* (list :permanent-noise)) (when *partial-matching* (list :similarities))))) (defun settable-declarative-parameters () "Returns the list of settable declarative parameters given the current values of global parameters. Used by the interface." (when *enable-rational-analysis* (nconc (if *base-level-learning* (list :creation-time :references) (list :base-level)) (when *associative-learning* (list :creation-cycle :needed :contexts)) (when *permanent-activation-noise* (list :permanent-noise))))) (defun production-parameter-fct (production &optional parameters) "Returns the value of the production parameter(s), or print all if none given." (let ((*verbose* t) (*goal-depth* 1) (value nil) (values nil)) (setf production (get-safe-production production)) (cond (production (signal-output *command-trace* "Parameters for production ~S:" production) ;;; updates production strength and parameters. (when *strength-learning* (strength production)) (when (numberp *parameters-learning*) (recompute-production-parameters production)) (cond (parameters (dolist (parameter parameters) (setf value (case parameter (:name (production-name production)) (:strength (production-strength production)) (:creation-time (production-creation-time production)) (:references (production-references production)) (:p (production-p production)) (:c (production-c production)) (:pg-c (production-pg-c production)) (:value (production-value production)) (:successes (production-successes production)) (:failures (production-failures production)) (:efforts (production-efforts production)) (:chance (production-chance production)) (:effort (production-effort production)) (:success (production-success production)) (:failure (production-failure production)) (t (signal-warn "NO PARAMETER ~A DEFINED FOR PRODUCTIONS (TRY : IN FRONT)." parameter) :error))) (push-last value values) (signal-output *command-trace* "~S ~6,3F" parameter value)) (signal-output *command-trace* "") values) (t (signal-output *command-trace* ":Chance ~6,3F~% :Effort ~6,3F" (production-chance production) (production-effort production)) (when *enable-rational-analysis* ; (signal-output *command-trace* ":Strength ~6,3F" (strength production)) (when (or *strength-learning* (numberp *parameters-learning*)) (signal-output *command-trace* ":Creation-Time ~6,3F" (production-creation-time production))) (when *strength-learning* (signal-output *command-trace* ":References ~6,3F" (production-references production)))) (if *enable-rational-analysis* (signal-output *command-trace* ":P ~6,3F~% :C ~6,3F~% :PG-C ~6,3F~%" (production-p production) (production-c production) (production-pg-c production)) (signal-output *command-trace* ":Value ~6,3F" (production-value production))) (when *parameters-learning* (signal-output *command-trace* ":Successes ~6,3F~% :Failures ~6,3F~% :Efforts ~6,3F~%~ ~1T:Success ~6,3F~% :Failure ~6,3F" (production-successes production) (production-failures production) (production-efforts production) (production-success production) (production-failure production))) (signal-output *command-trace* "") production))) (t :error)))) (defmacro set-parameter (slot parameter test warning &rest housekeeping) "Sets parameter of production p in slot if value passes test, otherwise issue warning." `(cond (,test (setf (,slot p) value) ,@housekeeping value) (t (signal-warn "PARAMETER ~A CANNOT TAKE VALUE ~S BECAUSE IT MUST BE ~:@(~A~)." ,parameter value ,warning) :error))) (defmacro pre-process-production-references (p value optimized) "Pre-process a specification of references into the proper format." `(progn (decf (production-time-stamp ,p) 1.0) ; pre-process references (when (listp ,value) (setf ,value (cons (length ,value) (copy-list ,value))) (let ((*optimized-learning* ,optimized)) (adapt-references ,value (production-creation-time ,p)))) (when (numberp ,value) (setf ,value (list ,value)) (let ((*optimized-learning* ,optimized)) (adapt-references ,value (production-creation-time ,p)))))) (defmacro pre-process-production-efforts (p value optimized references) "Pre-process a specification of efforts into the proper format." `(progn (decf (production-time-stamp ,p) 1.0) ; pre-process references (when (listp ,value) (let ((efforts 0.0)) (dolist (effort ,value) (incf efforts effort)) (setf ,value (cons efforts (copy-list ,value))))) (when (numberp ,value) (let ((ratio (round ,references))) (setf ,value (cons ,value (unless ,optimized (make-list ratio :initial-element (/ ,value ratio))))))))) (defun parameters-fct (p parameters) "Sets the parameters of production. Keyword arguments have been discontinued." (let ((values nil)) (setf p (get-safe-production p)) (if p (loop (unless parameters (recompute-production-parameters p) (return values)) (let* ((parameter (pop parameters)) (value (pop parameters))) (when (and (listp value) (eq (first value) 'quote)) (setf value (second value))) ;; for compatibility with evaluating versions (push-last (case parameter (:name (signal-warn "PARAMETER NAME CANNOT BE SET.") :error) (:strength (cond (*strength-learning* (signal-warn "PARAMETER STRENGTH CANNOT BE SET DIRECTLY WHEN STRENGTH LEARNING IS ENABLED: SET CREATION-TIME AND REFERENCES INSTEAD.") :error) (t (set-parameter production-strength :strength (numberp value) "a number")))) (:creation-time (decf (production-time-stamp p) 1.0) (set-parameter production-creation-time :creation-time (numberp value) "a number")) (:references (pre-process-production-references p value *optimized-learning*) (set-parameter production-references :references (or (numberp value) (listp value)) "a number or list of numbers")) (:value (unless (constantp value) (setf value (build-functional-parameter value (production-bindings p)))) (set-parameter production-value :value (or (numberp value) (functional-parameter-p value)) "a number of function")) (:pg-c (signal-warn "PRODUCTION PG-C CANNOT BE SET DIRECTLY: SET INDIVIDUAL PARAMETERS INSTEAD.") :error) (:p (cond (*parameters-learning* (signal-warn "PARAMETER P CANNOT BE SET DIRECTLY WHEN PARAMETERS LEARNING IS ENABLED: SET SUCCESSES AND FAILURES INSTEAD.") :error) (t (set-parameter production-p :p (and (numberp value) (>= value 0.0) (<= value 1.0)) "a number between 0.0 and 1.0")))) (:c (cond (*parameters-learning* (signal-warn "PARAMETER C CANNOT BE SET DIRECTLY WHEN PARAMETERS LEARNING IS ENABLED: SET SUCCESSES AND EFFORTS INSTEAD.") :error) (t (set-parameter production-c :c (and (numberp value) (>= value 0.0)) "a non-negative number")))) (:successes (pre-process-production-references p value (not (numberp *parameters-learning*))) (set-parameter production-successes :successes (or (numberp value) (listp value)) "a number or list of numbers")) (:failures (pre-process-production-references p value (not (numberp *parameters-learning*))) (set-parameter production-failures :failures (or (numberp value) (listp value)) "a number or list of numbers")) (:efforts (pre-process-production-efforts p value (not (numberp *parameters-learning*)) (first (production-successes p))) (set-parameter production-efforts :efforts (or (numberp value) (listp value)) "a number or list of numbers")) (:chance (unless (constantp value) (setf value (build-functional-parameter value (production-bindings p)))) (set-parameter production-chance :chance (or (and (numberp value) (>= value 0.0) (<= value 1.0)) (null value) (functional-parameter-p value)) "NIL or a function or number between 0.0 and 1.0")) (:effort (unless (constantp value) (setf value (build-functional-parameter value (production-bindings p)))) (set-parameter production-effort :effort (or (and (numberp value) (>= value 0.0)) (null value) (functional-parameter-p value)) "NIL or a non-negative number or function")) (:success (unless (constantp value) (setf value (build-functional-parameter value (production-bindings p)))) (set-parameter production-success :success (or (null value) (eq value t) (functional-parameter-p value)) "T or NIL or a function")) (:failure (unless (constantp value) (setf value (build-functional-parameter value (production-bindings p)))) (set-parameter production-failure :failure (or (null value) (eq value t) (functional-parameter-p value)) "T or NIL or a function")) (t (signal-warn "NO PARAMETER ~A DEFINED FOR PRODUCTIONS." parameter) :error)) values))) :error))) (defun spp-fct (parameters) "Inspects and sets production parameters." (let ((results nil)) (if (null parameters) ; print all parameters for all productions (dolist (production *procedural-memory*) (push-last (production-parameter-fct (cdr production)) results)) (dolist (description (if (or (keywordp (first parameters)) (keywordp (second parameters)) (and (listp (first parameters)) (null (second parameters)) (not (keywordp (second (first parameters)))))) (list parameters) parameters)) (when (atom description) (setf description (list description))) (if (keywordp (first description)) (dolist (production *procedural-memory*) (push-last (if (and (cdr description) (not (keywordp (second description)))) (parameters-fct (cdr production) description) (production-parameter-fct (cdr production) description)) results)) (dolist (production (if (atom (first description)) (list (first description)) (first description))) (push-last (if (and (cddr description) (not (keywordp (third description)))) (parameters-fct production (rest description)) (production-parameter-fct production (rest description))) results))))) results)) ;;; Run Time (defmacro subtype (sub type) "Tests whether sub is a subtype of supertype. Actually, the other way around since the listof supertypes is likely to be shorter." `(member ,type (wme-type-supertypes ,sub))) (defun match-lhs (production) "Matches the lhs of a production." (let ((gain 0.0)) (when *enable-rational-analysis* (setf gain (get-pg-c production)) (when *exp-gain-noise* (incf gain (noise *exp-gain-noise*)))) (if (and *utility-threshold* (< gain *utility-threshold*)) (signal-output (or *exact-matching-trace* *partial-matching-trace*) "Production ~S PG-C ~6,3F is below threshold: rejected." production (production-pg-c production)) (let ((i (production-size production)) (lhs (production-lhs production))) (signal-output (or *exact-matching-trace* *partial-matching-trace*) "Matching production ~S." production) (setf *instantiation* (production-instantiation production)) (loop (decf i 1) (when (= i 0) (return)) (setf (instantiation-variable *instantiation* i) nil)) ; (setf (instantiation-variable *instantiation* *instantiation-slots*) ; *wmfocus*) (dolist (initialization (production-initializations production)) (setf (instantiation-variable *instantiation* (car initialization)) (symbol-value (cdr initialization)))) (setf *extra-instantiation* (production-extra-instantiation production)) ; FIX: call just the car of lhs if :era is enabled. (when *enable-rational-analysis* (setf (instantiation-gain *instantiation*) gain) (setf lhs (car lhs))) (simulate-call lhs))) *conflict-set*)) ;;; Analogy (defun get-wmes (tree &optional (constants nil)) "Returns the list of wmes in tree. If constants is t, they are preserved." (let ((wme-list nil)) (dolist (leaf (if (consp tree) tree (when tree (list tree))) wme-list) (if (consp leaf) (setf wme-list (nconc wme-list (get-wmes leaf constants))) (let ((wme (if (wmep leaf) leaf (or (get-wme leaf) (if constants leaf :no))))) (unless (eq wme :no) (push-last wme wme-list))))))) (defun add-retrievals (retrievals variables constants singles &optional (differents nil)) "Adds components of retrievals to either variables or singles." (dolist (retrieval retrievals (values variables constants singles)) (when (and (atom retrieval) (not (wmep retrieval))) (setf retrieval (get-safe-wme retrieval))) (let ((wme (if (atom retrieval) retrieval (get-wme (first retrieval))))) (when (and wme (not (member wme variables :test #'equal))) (push wme variables))) (let ((wme-list nil)) (cond ((wmep retrieval) (setf wme-list (rest (wme-slot-wmes retrieval))) (when differents (do ((rest-list wme-list (rest rest-list))) ((null rest-list)) (setf wme-list (append (cdr (assoc (first rest-list) differents :test #'equal)) wme-list))))) (t (setf wme-list (get-wmes (rest retrieval))))) (dolist (wme wme-list) (unless (or (member wme variables :test #'equal) (member wme constants :test #'equal)) (cond ((member wme singles :test #'equal) (push wme variables) (setf singles (delete wme singles :test #'equal :count 1))) (t (push wme singles)))))))) (defun get-name-variable (value variables &optional (role nil)) "Gets the variable associated with value from the a-list variables. If no variable has been associated with value, then create one according to role. If no role is specified, then just variabilize the name value. If the name is taken, increment a counter until a free one is found." (let* ((name (if (wmep value) value (or (get-wme value) value))) (pair (assoc name variables :test #'equal))) (if pair (or (rest pair) (let* ((variable-name (concatenate 'string "=" (coerce-string (or role name)))) (variable (intern variable-name)) (counter 0)) (loop (if (and (not (eq role 'goal)) (rassoc variable variables)) (setf variable (intern (concatenate 'string variable-name (coerce-string (incf counter))))) (return))) (rplacd pair variable) variable)) name))) (defun wme-description (wme variables dont-cares &key (differents nil) (isa-slot t) (name wme) (role nil)) "Generates a pre-parsed production description of a wme, variabilizing variables. All slots are generated, except for dont-cares values. Slot negations are generated using differents. Also insert chunks and chunk types directly rather than as their names." (let* ((wmetype (wme-type wme)) (wmename (var-var> (get-name-variable name variables (or role (wme-type-name wmetype))))) (slot-value nil) (slot-list nil) (different nil)) (dolist (slot (wme-type-slots wmetype) (when (or slot-list isa-slot) (nconc (list wmename) (when isa-slot (list (list 'isa wmetype))) slot-list))) (setf slot-value (wme-slot wme (slot-index slot))) (when (and (or isa-slot (not (eq slot-value (wme-slot name (slot-index slot))))) (not (member slot-value dont-cares :test #'equal))) (push-last (list (slot-name slot) (get-name-variable slot-value variables (slot-name slot))) slot-list) (setf different (cdr (assoc slot-value differents :test #'equal))) (dolist (not different) (push-last (list '- (slot-name slot) (get-name-variable not variables)) slot-list)))))) (defun replace-variable-names (expression variables) "Replaces the values in expression using the variables alist." (if (listp expression) (let ((list nil)) (dolist (value expression list) (push-last (replace-variable-names value variables) list))) (get-name-variable expression variables))) (defun create-production (problem constraints solutions modified actions pop variables dont-cares differents) "Generates a production from analogy." ; production name based on goal type (let* ((type (wme-type-name (wme-type problem))) (name (safe-gentemp (coerce-string type))) (wme-variables nil) (wme nil) (lhs nil) (rhs nil)) ; associate the variable wmes to their variablized name, initialized to NIL (dolist (variable variables) (push (list variable) wme-variables)) ; problem and retrievals and lhs (push-last (wme-description problem wme-variables dont-cares :differents differents :role 'goal) lhs) (dolist (constraint constraints) (cond ((atom constraint) (setf wme (get-safe-wme constraint)) (when wme (push-last (wme-description wme wme-variables dont-cares :differents differents) lhs))) ((commandp (first constraint)) (push-last (replace-variable-names constraint wme-variables) lhs)) (t (setf wme (replace-variable-names constraint wme-variables)) (rplaca wme (var-var> (first wme))) (process-clause wme) (push-last wme lhs)))) ; subgoals in rhs (dolist (solution solutions) (push-last (wme-description solution wme-variables dont-cares :role 'subgoal) rhs)) (setf solutions (nreverse solutions)) (when (and modified (eq (wme-type problem) (wme-type modified))) (let ((main-goal (wme-description modified wme-variables dont-cares :isa-slot nil :name problem :role 'goal))) (when main-goal (push-last main-goal rhs)))) (let ((subgoal (pop solutions))) (if subgoal (push-last (list (if pop '!focus-on! '!push!) (get-name-variable subgoal wme-variables 'subgoal)) rhs) (when pop (push-last (list '!pop!) rhs)))) (dolist (solution solutions) (push-last (list '!push! (get-name-variable solution wme-variables 'subgoal)) rhs)) (dolist (action actions) (push-last (replace-variable-names action wme-variables) rhs)) (values name type lhs rhs))) (defun identical-productions (new old &optional (vars nil)) "Checks whether the production bodies old and new are identical." (do ((rest-new new (rest rest-new)) (rest-old old (rest rest-old))) ((or (null rest-new) (null rest-old)) (when (and (null rest-new) (null rest-old)) vars)) (let ((first-new (first rest-new)) (first-old (first rest-old))) ;; Fix: make sure that it both ends with > AND starts with = (when (and (retrievalp first-new) (variablep first-new)) (setf first-new (var>var first-new))) (when (and (retrievalp first-old) (variablep first-old)) (setf first-old (var>var first-old))) (cond ((and (variablep first-new) (variablep first-old)) (let ((first-new-assoc (cdr (assoc first-new vars :test #'eq))) (first-old-assoc (car (rassoc first-old vars :test #'eq)))) (if (or first-new-assoc first-old-assoc) (unless (and (eq first-old first-new-assoc) (eq first-new first-old-assoc)) (return nil)) (push (cons first-new first-old) vars)))) ((and (listp first-new) (listp first-old)) (unless (setf vars (identical-productions first-new first-old vars)) (return nil))) ((equal first-new first-old)) (t (return nil)))))) (defun reinforce-analogized-production (production) "Reinforces production by increasing the number of references." (when *strength-learning* (signal-output *production-compilation-trace* "Reinforcing production ~S." production) (add-reference (production-references production))) (when *parameters-learning* (signal-output *production-compilation-trace* "Updating parameters of production ~S." production) (add-reference (production-successes production) (not (numberp *parameters-learning*))) (add-reference (production-efforts production) (not (numberp *parameters-learning*)) (production-effort production) (production-effort production)) (recompute-production-parameters production))) (defun compile-or-select-production (name type lhs rhs success failure &optional (documentation nil)) "Either create a new production or select an existing identical one. Return a second value indicating whether it is an existing production (t) or not (nil)." (dolist (production (wme-type-productions (get-safe-type type name)) (let ((new-production (compile-production name lhs rhs documentation))) (signal-output *production-compilation-trace* "Compiling Production ~A." new-production) (parameters-fct name (nconc (list :success (if success t nil) :failure (if failure t nil)) *production-compilation-parameters*)) (values new-production nil))) (when (identical-productions (cons lhs rhs) (production-text production)) (signal-output *production-compilation-trace* "Recreating Production ~A." production) (when *reinforce-analogized-production* (reinforce-analogized-production production)) (return (values production t))))) (defun solve-by-analogy (dependency) "Main function to generate a production based on dependency. Returns the new analogized production." (signal-output *production-compilation-trace* "Analogizing to dependency ~S" dependency) (let ((stack-values (get-slot-value dependency 'stack)) (stack-solutions nil) (stack-success nil) (stack-failure nil)) (dolist (value (cond ((listp stack-values) stack-values) ((wmep stack-values) (list (wme-name stack-values))) (t nil))) (cond ((eq value 'success) (setf stack-success t)) ((eq value 'failure) (setf stack-failure t)) (t (push-last (if (wmep stack-values) stack-values (get-wme-or-constant value)) stack-solutions)))) (let ((problem (first (get-wmes (get-slot-value dependency 'goal)))) (solutions (nconc (get-wmes (get-slot-value dependency 'subgoals)) stack-solutions)) (modified (first (get-wmes (get-slot-value dependency 'modified)))) (constraints (get-slot-value dependency 'constraints)) (actions (get-slot-value dependency 'actions)) (success (or (get-slot-value dependency 'success) stack-success)) (failure (or (get-slot-value dependency 'failure) stack-failure)) (variables (get-wmes (get-slot-value dependency 'generals) t)) (constants (get-wmes (get-slot-value dependency 'specifics) t)) (dont-cares (get-wmes (get-slot-value dependency 'dont-cares) t)) (differents (get-slot-value dependency 'differents)) (pop nil) (singles nil)) ;; Replace wme names in differents with the wmes themselves (when (and differents (not (listp (first differents)))) (setf differents (list differents))) (dolist (different differents) (loop (unless different (return)) (let ((wme (get-wme (first different)))) (when wme (rplaca different wme))) (pop different))) ;; copy the constraints and action lists ;; checking for single conditions (setf constraints (if (or (not (listp constraints)) ; activation (commandp (first constraints)) ; !eval! (eq (second constraints) 'isa)) ; description (list constraints) (copy-list constraints))) (setf actions (if (not (listp actions)) ; !pop! (list (list actions)) (if (commandp (first actions)) ; (!push! =goal) (list actions) (copy-list actions)))) ;; check modified goal (when (and modified (not (eq (wme-type problem) (wme-type modified)))) (push-last modified solutions) (setf modified nil) (setf pop t)) (signal-output *production-compilation-trace* "The goal is ~S and the subgoals are~{ ~S~}" problem (or solutions '(none))) ;; determine variables and constants (multiple-value-setq (variables constants singles) (add-retrievals (cons problem constraints) variables constants singles differents)) (multiple-value-setq (variables constants singles) (add-retrievals (append solutions (when modified (list modified)) actions) variables constants singles)) ;; compile the production (multiple-value-bind (name type lhs rhs) (create-production problem constraints solutions modified actions (or success failure pop) variables dont-cares differents) (compile-or-select-production name type lhs rhs success failure))))) (defun whynot-dependency-fct (dependencies) "Tests production compilation with the dependencies." (let ((*production-compilation-trace* *command-trace*) (*exact-matching-trace* *command-trace*) (*partial-matching-trace* *command-trace*) (*verbose* t) (*goal-depth* 1) (productions nil)) (dolist (dependency dependencies productions) (let ((production (solve-by-analogy (get-safe-wme dependency)))) (push-last (production-name production) productions) (pdisable-fct (list production)))))) (defun set-compilation-parameters-fct (params) "Sets the parameters for compiled productions." (setf *production-compilation-parameters* params)) #| ;;; New production compilation mechanisms (defmacro condition-value (condition) "Return the value in a condition." `(if (test-modifier (first ,condition)) (third ,condition) (second ,condition))) (defmacro condition-slot (condition) "Return the slot in a condition." `(if (test-modifier (first ,condition)) (second ,condition) (first ,condition))) (defun condition-variables (conditions &optional (retrieval *retrieval*)) "Returns the variables in a retrieval condition paired with their value." (let ((variables nil)) (dolist (condition conditions variables) (let ((value (condition-value condition))) (when (variablep value) (push (cons value (chunk-slot-value-fct retrieval (condition-slot condition))) variables)))))) (defun merge-conditions (dominant other exception) "Merge the other condition into the dominant condition after removing conflicts with exception. Returns as result the variable-value pairs that should be replaced." (let ((variables nil)) (dolist (condition (rest other) variables) (let ((conflict (assoc (condition-slot condition) (rest exception)))) (if conflict (when (variablep (condition-value condition)) (push (cons (condition-value condition) (second conflict)) variables)) (unless (member condition dominant :test #'equal) ; do not duplicate conditions (push-last condition dominant))))))) (defun compose-production-parameters (new old1 old2) (let ((effort (- (+ (production-effort old1) (production-effort old2)) *default-action-time*)) (c (+ (production-c old1) *cost-penalty*)) (p (production-p old1))) (when *production-compilation-trace* (pprint-production new *production-compilation-trace*)) (if *parameters-learning* (spp-fct (list new :effort effort :successes (* p *initial-experience*) :failures (* (- 1.0 p) *initial-experience*) :efforts (* c *initial-experience*))) (spp-fct (list new :effort effort :p p :c c))))) (defun compose (prod1 prod2) "Compose productions prod1 and prod2 into a new composite one. The perceptual-motor buffers are composed straightforwardly, since any problematic intersection has been ruled out. The retrieval buffers will be proceduralized if action in prod1 and test in prod2. The goal buffers will be combined according to the special-purpose rules." (let ((test1 (copy-tree (first (production-text prod1)))) (action1 (copy-tree (rest (production-text prod1)))) (test2 (copy-tree (first (production-text prod2)))) (action2 (copy-tree (rest (production-text prod2)))) (lhs nil) (rhs nil)) ;; if request for retrieval in action1 and test in test2, note the variables ;; appearing in those conditions (including =retrieval), delete the retrievals, ;; then replace the variables with their values in the remaining conditions (let ((retrieval-action1 (assoc '+retrieval> action1)) (retrieval-test2 (assoc '=retrieval> test2))) (when (and retrieval-action1 retrieval-test2) (let ((variables-prod1 (condition-variables (rest retrieval-action1))) (variables-prod2 (cons (cons '=retrieval *retrieval*) (condition-variables (rest retrieval-test2))))) (setf action1 (delete retrieval-action1 action1)) (setf test2 (delete retrieval-test2 test2)) (setf test1 (nsublis variables-prod1 test1)) (setf action1 (nsublis variables-prod1 action1)) (setf test2 (nsublis variables-prod2 test2)) (setf action2 (nsublis variables-prod2 action2)) ;; handle the insertion of !eval! (equal ) in test1 (dolist (variable variables-prod1) (when (assoc (var-var> (car variable)) test1) ; if buffer appears in lhs (setf test1 (append test1 `((!eval! (equal ,(car variable) ',(cdr variable)))))))) ))) ;; handle the goal merging ; (format t "~%TEST1: ~S ~%ACTION1: ~S~%TEST2: ~S~%ACTION2: ~S~%" ; test1 action1 test2 action2) (let ((c1 (assoc '=goal> test1)) (c2 (assoc '=goal> test2)) (a1 (assoc '=goal> action1)) (a2 (assoc '=goal> action2)) (n1 (assoc '+goal> action1)) (n2 (assoc '+goal> action2)) (variables-prod1 nil) (variables-prod2 nil)) (cond ((not n1) ;; no goal resetting: C1+(C2-A1), A2+(A1-A2), N2 if present (when (and (not c1) c2) (setf c1 (list (first c2) (second c2))) (push c1 action1)) (setf variables-prod2 (append variables-prod2 (merge-conditions c1 c2 a1))) ;; merge conditions into c1 (setf test2 (delete c2 test2)) ;; remove c2 (when (and a1 a2) (setf variables-prod1 (append variables-prod1 (merge-conditions a2 a1 a2))) ;; merge actions into a2 (setf action1 (delete a1 action1)))) ; remove a1 ((not n2) ;; 1 goal resetting: C1, A1 and A2+(N1-A2) (when a2 ;; merge N1 into A2 (rplaca a2 '+goal>) (unless (eq (first (second a2)) 'isa) (rplacd a2 (cons (second n1) (rest a2)))) (setf variables-prod1 (append variables-prod1 (merge-conditions a2 n1 a2))) (setf action1 (delete n1 action1)) (setf test2 (delete c2 test2)))) (t ;; 2 goal resetting: C1, A1, N2, unless the goal reset is the same (setf action1 (delete n1 action1)) (setf test2 (delete c2 test2)) (setf action2 (delete a2 action2)))) (setf test1 (nsublis variables-prod1 test1)) (setf action1 (nsublis variables-prod1 action1)) (setf test2 (nsublis variables-prod2 test2)) (setf action2 (nsublis variables-prod2 action2)) ;; handle the insertion of !eval! (equal ) in test1 and test2 (dolist (variable variables-prod1) (when (assoc (var-var> (car variable)) test1) ; if buffer appears in lhs (setf test1 (append test1 `((!eval! (equal ,(car variable) ',(cdr variable)))))))) (dolist (variable variables-prod2) (when (assoc (var-var> (car variable)) test2) ; if buffer appears in lhs (setf test2 (append test2 `((!eval! (equal ,(car variable) ',(cdr variable)))))))) ) ; (format t "~%TEST1: ~S ~%ACTION1: ~S~%TEST2: ~S~%ACTION2: ~S~%" ; test1 action1 test2 action2) ;; merge everything (setf lhs (append test1 test2)) (setf rhs (append action1 action2)) ; (format t "~%LHS: ~S~%RHS: ~S~%" lhs rhs) ;; create new production or reinforce existing one (let ((*reinforce-analogized-production* nil) (success (or (production-success prod1) (production-success prod2))) (failure (or (production-failure prod1) (production-failure prod2)))) (multiple-value-bind (new-production identical) (compile-or-select-production (safe-gentemp "PRODUCTION") (second (assoc 'isa (rest (assoc '=goal> lhs)))) lhs rhs success failure) (unless identical (compose-production-parameters new-production prod1 prod2)) (spp-fct (list new-production)) new-production)))) (defun filter-buffers (patterns buffers) (let ((keywords nil)) (dolist (pattern patterns keywords) (when (retrievalp (first pattern)) (let ((keyword (first pattern))) (when (member keyword buffers) (push-last (var=var keyword) keywords))))))) (defun composable (first-actions second-tests) "Determines whether the productions first and second can be composed. That means: No action (+ or -) on a buffer in first that is tested (=) in second. No retrieval request in first if second tests against an error." (let ((pm-actions '(+visual-location> +visual> +aural> +aural-location> +manual> +vocal> -visual-location> -visual> -aural> -aural-location> -manual> -vocal>)) (pm-tests '(=visual-location> =visual> =aural> =aural-location> =manual> =vocal>))) (and (null (intersection (filter-buffers first-actions pm-actions) ;; pm actions in 1st (filter-buffers second-tests pm-tests))) ;; pm test in 2nd (or (not (assoc '+retrieval> first-actions)) ;; no retrieval request or (not (eq (second (second (assoc '=retrieval> second-tests))) (get-type 'error))) ;; no error test )))) (defun compose-and-proceduralize (&optional (instantiation *instantiation*)) "Composes instantiation with the previous instantiation stored in a special variable. Only works for ACT-R 5.0 buffer syntax. Rules of composition have been generalized." (let ((new-production nil)) (when (and *previous-instantiation* (<= *time* (+ (first *previous-instantiation*) *threshold-time*))) (let ((previous-production (instantiation-production (rest *previous-instantiation*))) (production (instantiation-production instantiation))) (when (composable (rest (production-text previous-production)) (first (production-text production))) (setf new-production (compose previous-production production))))) (setf *previous-instantiation* (cons *time* (copy-instantiation instantiation))) new-production)) |# ;;; moved here before they are used by john's code to prevent warnings (defmacro no-output (&rest forms) "Evaluates forms with *command-trace* turned off." `(let ((*command-trace* nil)) ,@forms)) (defmacro sgp (&rest parameters) "Sets global parameters. Checks values and performs housekeeping duties." `(sgp-fct ',parameters)) ;;; John's compilation code integrated as is for now ;The change in the code below was motivated to deal with goal merging that resulted in different names ;for goals. It is also hacked up to prevent print-outs ;This function generalizes a test in compose production ;That function makes reference to a global variable *merge* (defun same-goal-test (g1 g2) (or (eq g1 g2) (eq (headere g1)(car *merge*)))) #| ;;; Removed because of duplication (defun delete-wme (wme &optional (identical nil)) "Deletes a given wme node from the type memory and the hash table. Zeroes all its slots, and only proceed when it is not referenced. If identical is specified, any remaining reference to wme will be replaced with identical. Also replace occurrences of wme in buffers, retrieval scheduler and instantiation variables." (dolist (parent (rest (wme-fan wme))) (unless (eq parent wme) (if identical ; replace references to wme in chunk slots with identical (dotimes (index (wme-type-size (wme-type parent))) (when (eq (wme-slot parent index) wme) (set-slot-value parent index identical))) (signal-output *command-trace* "CHUNK ~S IS STILL REFERENCED IN CHUNK ~S WHEN DELETED." wme parent)))) ;; Clear occurences of wme in buffers (dolist (buffer *buffer-keywords*) (setf buffer (cdr buffer)) (when (eq (symbol-value buffer) wme) (setf (symbol-value buffer) identical))) ;; same for retrieval scheduler (when (eq (cdr *retrieval-scheduler*) wme) (rplacd *retrieval-scheduler* identical)) ;; Clear occurences of wme in instantiation variables (let ((instantiation *instantiation*)) (when instantiation (dotimes (variable (production-size (instantiation-production instantiation))) (when (eq (instantiation-variable instantiation variable) wme) (setf (instantiation-variable instantiation variable) identical))))) ;; When merging print a warning message (when identical ;commenting out the print-out ; (if *verbose* (signal-output *cycle-trace* "Merging chunk ~S into chunk ~S" wme identical)) ;setting the global variable *merge* for its use in the above (setf *merge* (list wme identical))) (let ((wmetype (wme-type wme))) (decf *wme-number* 1.0) (dolist (slot (wme-type-slots wmetype)) (set-slot-value wme (slot-index slot) nil)) (remove-name (wme-name wme)) (setf (wme-type-wmes wmetype) (delete wme (wme-type-wmes wmetype) :test #'eq :count 1)) (save-state-change :delete-wme wme))) (defun identical-productions (new old &optional (vars nil)) "Checks whether the production bodies old and new are identical." (do ((rest-new new (rest rest-new)) (rest-old old (rest rest-old))) ((or (null rest-new) (null rest-old)) (when (and (null rest-new) (null rest-old)) vars)) (let ((first-new (first rest-new)) (first-old (first rest-old))) ;; Fix: make sure that it both ends with > AND starts with = (when (and (retrievalp first-new) (variablep first-new)) (setf first-new (var>var first-new))) (when (and (retrievalp first-old) (variablep first-old)) (setf first-old (var>var first-old))) (cond ((and (variablep first-new) (variablep first-old)) (let ((first-new-assoc (cdr (assoc first-new vars :test #'eq))) (first-old-assoc (car (rassoc first-old vars :test #'eq)))) (if (or first-new-assoc first-old-assoc) (unless (and (eq first-old first-new-assoc) (eq first-new first-old-assoc)) (return nil)) (push (cons first-new first-old) vars)))) ((and (listp first-new) (listp first-old)) (unless (setf vars (identical-productions first-new first-old vars)) (return nil))) ((equal first-new first-old)) (t (return nil)))))) (defun learn-parameters (success failure &key (latency 0.0) (instantiation *instantiation*)) "Applies the parameters learning equations to instantiation. The success and/or failure of the goal has already been determined and is passed along with the latency of this instantiation and the instantiation itself (if it exists)." (let ((production (when instantiation (instantiation-production instantiation)))) ; (when (and (not success) (not failure) instantiation) ;; see if goal changes: success ; (let ((goal-index (car (rassoc '*wmfocus* (production-initializations production))))) ; (when (and goal-index ;; not always mention of the goal in the production ; (not (equal *wmfocus* (instantiation-variable instantiation goal-index)))) ; (setf success t)))) ;;; success by default upon goal changes (cond ((and success failure) ;; clear the history (setf *previous-instantiations* nil)) ((or success failure) ;; register success or failure for all productions (when production (push (cons production (- *time* latency)) *previous-instantiations*)) (dolist (production-time *previous-instantiations*) (let* ((previous-production (car production-time)) (previous-time (cdr production-time)) (effort (- *time* previous-time))) (add-reference (if success (production-successes previous-production) (production-failures previous-production)) (not (numberp *parameters-learning*)) 1.0 previous-time) (add-reference (production-efforts previous-production) (not (numberp *parameters-learning*)) effort effort) (recompute-production-parameters previous-production))) (setf *previous-instantiations* nil)) (t ;; simply add the production (when production (push (cons production (- *time* latency)) *previous-instantiations*)))))) |# (defun get-set (ins) (cond (ins (let ((list (get-instantiation ins))) (list (aref ins 0) (second (second (caar list))) (first list) (cdr list)))) (t nil))) (defun get-instantiation (&optional (instantiation *instantiation*) (trace *command-trace*) (format *production-trace*)) "Pretty-prints an instantiation." (let ((production (instantiation-production instantiation))) (when (eq trace 'short) (setf trace t)) (if (eq format 'short) (dolist (binding (production-bindings production)) (signal-output trace " ~A: ~A" (variable-name binding) (if (integerp (variable-index binding)) (or (instantiation-variable instantiation (variable-index binding)) "VARIABLE STILL UNBOUND") "RETURN VARIABLE STILL UNDETERMINED"))) (let ((bindings nil) (text nil)) (dolist (binding (production-bindings production)) (when (integerp (variable-index binding)) (let ((value (instantiation-variable instantiation (variable-index binding)))) (when value (push (cons (variable-name binding) value) bindings) (push (cons (var-var> (variable-name binding)) (var-var> value)) bindings))))) (setf text (sublis bindings (production-text production))) text)))) ;THIS PRODUCTION IS CALLED AFTER EACH CYCLE. IT WILL CALL COMPOSE-PRODUCTION TO TRY TO COLLAPSE THE PREVIOUS ;AND CURRENT PRODUCTION UNLESS A PERIOD OF TIME HAS ELAPSED UNUSED BETWEEN THE TWO PRODUCTIONS -- IN WHICH ;CASE THE INFERENCE IS THAT WE WERE WAITING FOR SOME EXTERNAL EVENT. (defun store-instantiation (i) (let (hold) (cond (*instantiation* (setf hold (get-set i)) (cond ((and (rest *previous-instantiation*) (< (- *time* (first *previous-instantiation*)) *threshold-time*)) (compose-production (rest *previous-instantiation*) hold))) (setf *previous-instantiation* (cons *time* hold)))))) (defun legal-condition (ins) (do ((temp (get-condition ins) (cdr temp))) ((null temp) t) (cond ((not (member (caar temp) '(=goal> =retrieval> =visual> =visual-location> =visual-state> !eval! =manual> =manual-state) :test 'equal)) (return nil))))) (defun legal-action (ins) (do ((temp (get-action ins) (cdr temp))) ((null temp) t) (cond ((not (member (caar temp) '(=goal> +goal> +retrieval> +visual> +visual-location> -visual> -visual-location> -retrieval> +manual>) :test 'equal)) (return nil))))) (defun legal-pair (x y) (and (legal-condition x) (legal-action x) (legal-condition y) (legal-action y))) (defun compose-production (x y) ;The legal pair test has been asserted simply to abort any attempt to compose productions that have things in them other ;that what is considered kosher. (if (legal-pair x y) ;X IS THE FIRST PRODUCTION AND Y THE SECOND ;WE IDENTIFY THE PATTERNS AND INSTANTIONS OF THESE TWO PRODUCTIONS IN THE NEXT 8 ASSIGNMENTS (let* ((renamings (renamings (car x) (car y))) ;renamings has been introduced to avoid any accidental use of the same variable across productions -- this is a poor version of achieving ;a thorough unification (cond-pat1 (get-condition x)) ;pattern in condition of first production (cond-pat2 (substitute-vars (get-condition y) renamings)) ;pattern in condition of second production (act-pat1 (get-action x)) ;pattern in action of first production (act-pat2 (substitute-vars (get-action y) renamings)) ;pattern in action of second production (cond-ins1 (third x)) ;instantiation of condition of first production (cond-ins2 (third y)) ;instantiation of condition of second production (act-ins1 (fourth x)) ;instantiation of action of first production (act-ins2 (fourth y)) ;instantiation of action of second production ;EXTRACTION OF BUFFERS -- THIS IS REPRESENTED AS A PAIRING OF THE PATTERN AND INSTANTIAION (gc1 (get-terms cond-pat1 cond-ins1 '=goal>)) ; goal buffer in the condition of first production (gc2 (get-terms cond-pat2 cond-ins2 '=goal>)) ; goal buffer in the condition of second production (ga1 (get-terms act-pat1 act-ins1 '=goal>)) ; goal buffer in the action of first production (ga2 (get-terms act-pat2 act-ins2 '=goal>)) ; goal buffer in the action of second production (rc1 (get-terms cond-pat1 cond-ins1 '=retrieval>)) ; retrieval buffer in the condition of first production (rc2 (get-terms cond-pat2 cond-ins2 '=retrieval>)) ; retrieval buffer in the condition of second production (ra1 (or (get-terms act-pat1 act-ins1 '+retrieval>)(get-terms act-pat1 act-ins1 '-retrieval>))) ; retrieval buffer in the action of first production (ra2 (or (get-terms act-pat2 act-ins2 '+retrieval>)(get-terms act-pat2 act-ins2 '-retrieval>))) ; retrieval buffer in the action of second production (ras1 (get-terms act-pat1 act-ins1 '=retrieval>)) (ras2 (get-terms act-pat2 act-ins2 '=retrieval>)) ;deals with cases where we are resetting the the contents of a chunk (vc1 (get-terms cond-pat1 cond-ins1 '=visual>)) ; visual-object buffer in the condition of first production (vl1 (get-terms cond-pat1 cond-ins1 '=visual-location>)) ; visual-location buffer in the condition of first production (vs1 (get-terms cond-pat1 cond-ins1 '=visual-state>)) ; visual-state buffer in the condition of first production (vc2 (get-terms cond-pat2 cond-ins2 '=visual>)) ; visual-object buffer in the condition of second production (vl2 (get-terms cond-pat2 cond-ins2 '=visual-location>)) ; visual-location buffer in the condition of second production (vs2 (get-terms cond-pat2 cond-ins2 '=visual-state>)) ; visual-state buffer in the condition of second production (va1 (or (get-terms act-pat1 act-ins1 '+visual>)(get-terms act-pat1 act-ins1 '-visual>))) ; visual-object buffer in the action of first production (va2 (or (get-terms act-pat2 act-ins2 '+visual>)(get-terms act-pat2 act-ins2 '-visual>))) ; visual-object buffer in the action of second production (vla1 (or (get-terms act-pat1 act-ins1 '+visual-location>)(get-terms act-pat1 act-ins1 '-visual-location>))) (vla2 (or (get-terms act-pat2 act-ins2 '+visual-location>)(get-terms act-pat2 act-ins2 '-visual-location>))) (eval1 (car (get-terms cond-pat1 cond-ins1 '!eval!))) (eval2 (car (get-terms cond-pat2 cond-ins2 '!eval!))) (mc1 (get-terms cond-pat1 cond-ins1 '=manual>)) (mc2 (get-terms cond-pat2 cond-ins2 '=manual>)) (ms1 (get-terms cond-pat1 cond-ins1 '=manual-state>)) (ms2 (get-terms cond-pat2 cond-ins2 '=manual-state>)) ; manual buffer in the condition of second production ;more complete treatment of buffers (ma1 (or (get-terms act-pat1 act-ins1 '+manual>) (get-terms act-pat1 act-ins1 '-manual>))) ; manual buffer in the action of first production (ma2 (or (get-terms act-pat2 act-ins2 '+manual>)(get-terms act-pat2 act-ins2 '-manual>))) ; manual buffer in the action of second production (n1 (or (get-terms act-pat1 act-ins1 '+goal>) (get-terms act-pat1 act-ins1 '=newgoal>)) ) ; newgoal buffer in the action of first production (n2 (or (get-terms act-pat2 act-ins2 '+goal>) (get-terms act-pat2 act-ins2 '=newgoal>)))) ; newgoal buffer in the action of second production (declare (ignore-if-unused mc1 mc2)) ; THIS DEALS WITH THE CASE IN WHICH WE CAN SIMPLY COMPOSE THE TWO CONDITIONS BUT FIRST WE TEST FOR ALL THE CASES WHERE THIS IS FORBIDDEN (cond ( (and (not n1) ;one does not want to simply compose over a switch in the goal but see below (same-goal-test (car (second gc1)) (car (second gc2))) ;then they should be the same goal ;the line above is the generalized same goal test and the line below avoids the situation of direction retrieval of the goal (not (equal (first (second (first ra1))) '=goal)) (not (and va1 vc2)) (not (and Va1 va2)) (not (or ras1 ras2)) (not (and vla1 vl2)) (not (and vla1 vla2)) ;one does not want to compose when there is a contingency between the visual processing (not (and ma1 ma2)) ;one does not want to have jamming actions (not (and rc2 (equal (wme-type-name (second (second (car rc2)))) 'error)))) ;one also does not want to compose over a retrieval failure ;IF WE ARE GOING TO COMPOSE THE PRODUCTIONS WE NEED TO GO ABOUT CALCULATING WHAT THE BUFFER SPECIFICATIONS WILL BE IN THE COMPOSED PRODUCTION ;IF THERE IS A RETRIEVAL REQUEST IS THE FIRST AND A TEST WE WILL DELETE THESE ;BEYOND THIS WE MUST MAKE SURE THAT THE VARIABLES ARE CONSTRAINED TO BE WHAT THEY WOULD HAVE BEEN IN THIS RETRIEVAL REQUEST ;MORE GENERALLY WE NEED TO CALCULATE A CONSISTENT SET OF VARIABLE NAMES BETWEEN THE TWO PRODUCTIONS (let* ((mapping1 (append (extract-map (first ra1) (second ra1)) ;any variables in the first's retrieval request must be replaced by constants (extract-constant-map (first ga1) (first gc2)) ;deal with case were second condition constrains variable in first action (modify1 (first ra1) (second ra1)) ;Deals with the mapping produced required when there is direct assignment to a retrieval request. For ;reasons that are unclear to me any more I re-ordered the retrieval and goal mappings (hack (first ga1) (first gc2) (first rc2) (second rc2)))) (rt (retrieval-test mapping1 )) (mapping2 (extend-map (append (modify (first ra1) (second ra1)) ;=retrieval in second production replaced with direct assignment (extract-map (first rc2) (second rc2)) ;any variables in the seconds retrieval test but be replaced by constants (extract-map (first gc2) (first ga1)) ;any variables in the second's goal test must be replace by ;extending the mapping to include terms in the retrieval buffer (extract-map (first vc2) (first vc1)) (extract-map (first gc2) (first gc1))) ;corresponding terms from the first action or condition mapping1));these substitutions for the second production must be made consistent with the substitution for the first (gc (chunk-merge-condition (substitute-vars (car gc1) mapping1) ;the goal buffer must be specific to the retrieval (chunk-dif (substitute-vars (car gc2) mapping2) ;and include any additional tests from the second production (substitute-vars (car ga1) mapping1)))) ;that were not created in the first's action (ga (chunk-merge-action (substitute-vars (car ga2) mapping2) ;the goal action must combine the action of the second and the action of (substitute-vars (car ga1) mapping1))) ;the first giving preference to the second (ma (or (substitute-vars (car ma1) mapping1) (substitute-vars (car ma2) mapping2))) ;whichever has a manual condition (rc (substitute-vars (car rc1) mapping1)) ;maintain the instantiated retrieval test from the first (ra (substitute-vars (car ra2) mapping2)) ;maintain the instantiated retrieval request from the second (vl (or (substitute-vars (car vl1) mapping1) (substitute-vars(car vl2) mapping2))) ; one of the tests of visual location (vs (or (substitute-vars (car vs1) mapping1) (substitute-vars (car vs2) mapping2))) ;one of the visual states -- my colon is a hack trying to deal with :visual ;Substitution is required in these cases as well (ms (or (substitute-vars (car ms1) mapping1) (substitute-vars (car ms2) mapping2))) ;more complete treatment of buffers (vc (or (substitute-vars (car vc1) mapping1) (substitute-vars (car vc2) mapping2))) ;one of the visual object tests (va (or (substitute-vars (car va1) mapping1) (substitute-vars (car va2) mapping2))) ;one of the visual object requests ;Substitution is required in these cases as well (vla (location-filter (or(substitute-vars (car vla1)mapping1) (substitute-vars (car vla2)mapping2)))) ;The location filter is just a hack to deal with troubles I have had with wmes versus symbols (eval (eval-merge rt (substitute-vars eval1 mapping1) (substitute-vars eval2 mapping2))) ;reatment of evals (na (substitute-vars (car n2) mapping2)) ;the goal switch made consistent ;NOW THE ACTUAL CALCULATION OF THE NEW PRODUCTION CAN BEGIN (new-condition (remove-nils(list gc rc vl vs vc ms eval)) ) ;the new condition with all buffers specified ;ms and eval have been added. The eval computation and the old rt have been absorbed by val (new-action (remove-nils (list vla va ga ra ma na) )) ;the new action with all buffers specified (production-name (new-name-fct "PRODUCTION")) ;create a new name for the production (production (compile-or-select-production production-name (second (second (first cond-pat1))) new-condition new-action nil nil))) ;call code to create production (when production (finish-up production (car x) (car y))))) ;when a production was created set its parameters etc ;THE CODE BELOW DEALS WITH A CASE IN WHICH ONE CAN COMPOSE TWO PRODUCTIONS EVEN IF THEY BOTH INVOLVE NEWGOALS. THIS IS WHEN THE SECOND NEWGOAL IS ;RETURNING TO THE GOAL OF THE FIRST PRODUCTION. THUS THE FIRST PRODUCTION PUSHES AND THE SECOND POPS ((and n1 n2 (equal (headere (car (second gc1))) (car (second (second n2)))) ;test for this condition (not (equal (first (second (first ra1))) '=goal)) ;the line above is the generalized same goal test (not (and va1 vc2)) (not (and Va1 va2)) (not (or ras1 ras2)) (not (and vla1 vl2)) (not (and vla1 vla2)) ;one does not want to compose when there is a contingency between the visual processing (not (and ma1 ma2)) ;one does not want to have jamming actions (not (and rc2 (equal (wme-type-name (second (second (car rc2)))) 'error)))) ;one also does not want to compose over a retrieval failure ;Copying of the same tests used for the case above (let* ((mapping1 (append (extract-map (first ra1) (second ra1)) ;any variables in the first's retrieval request must be replaced by constants (modify1 (first ra1) (second ra1)) ;Deals with the mapping produced required when there is direct assignment to a retrieval request (extract-map (first n1) (first gc2)))) ;terms in the first production must be specific to how the goal was tested ;For reasons that are unclear to me any more I re-ordered the retrieval and goal mappings (rt (retrieval-test mapping1 )) (mapping2 (extend-map (append (modify (first ra1) (second ra1)) ;Correction of a mistake in place of (extract-map (first gc2) (first ga1) ;=retrieval in second production replaced with direct assignment (extract-map (first rc2) (second rc2));any variables in the seconds retrieval test but be replaced by constants (extract-map (first vc2) (first vc1)) ;extending the mapping to include terms in the retrieval buffer (extract-map (first gc2) (first n1))) ;corresponding terms from the first action or condition ;Correction of a mistake mapping1)) ;these substitutions for the second production must be made consistent with the substitution for the first (gc (substitute-vars (car gc1) mapping1)) ;rename terms in first goal test (ga (substitute-vars (car ga1) mapping1)) ;rename terms in first goal change (ma (or (substitute-vars (car ma1) mapping1) (substitute-vars (car ma2) mapping2))) ;whichever has a manual condition (rc (substitute-vars (car rc1) mapping1)) ;maintain the instantiated retrieval test from the first (ra (substitute-vars (car ra2) mapping2)) ;maintain the instantiated retrieval request from the second (vl (or (substitute-vars (car vl1) mapping1)(substitute-vars (car vl2) mapping2))) ; one of the tests of visual location (vs (or (substitute-vars (car vs1) mapping1) (substitute-vars (car vs2) mapping2))) ;Substitution is required in these cases as well (vla (or(substitute-vars (car vla1)mapping1)(substitute-vars (car vla2)mapping2))) (vla (location-filter (or(substitute-vars (car vla1)mapping1) (substitute-vars (car vla2)mapping2)))) ;Visual locations appearing in the action -- the first setting is dead code ;The location filter is just a hack to deal with troubles I have had with wmes versus symbols (ms (or (substitute-vars (car ms1) mapping1) (substitute-vars (car ms2) mapping2))) ;more complete treatment of buffers (eval (eval-merge rt (substitute-vars eval1 mapping1) (substitute-vars eval2 mapping2))) ;treatment of evals (vc (or (substitute-vars (car vc1) mapping1) (substitute-vars (car vc2) mapping2))) ;one of the visual object tests (va (or (substitute-vars (car va1) mapping1) (substitute-vars (car va2) mapping2))) ;one of the visual object requests ;NOW THE ACTUAL CALCULATION OF THE NEW PRODUCTION CAN BEGIN (new-condition (remove-nils (list gc rc vl vs vc ms eval) )) ;the new condition with all buffers specified ;ms and eval have been added. The eval computation and the old rt have been absorbed by val (new-action (remove-nils (list vla va ga ra ma) )) ;the new action with all buffers specified (production-name (new-name-fct "PRODUCTION")) ;create a new name for the production (production (compile-or-select-production production-name (second (second (first cond-pat1))) ;call code to create production new-condition new-action nil nil))) ;call code to create production (declare (ignore-if-unused vla)) (when production (finish-up production (car x) (car y))))) ;when a production was created set its parameters etc ((and n1 (not n2) (equal (car (second (car n1))) 'isa) (not (and va1 vc2)) (not (and Va1 va2)) (not (or ras1 ras2)) ;the line below avoids the situation of direction retrieval of the goal (not (equal (first (second (first ra1))) '=goal)) (not (and vla1 vl2)) (not (and vla1 vla2)) ;one does not want to compose when there is a contingency between the visual processing (not (and ma1 ma2)) ;one does not want to have jamming actions (not (and rc2 (equal (wme-type-name (second (second (car rc2)))) 'error)))) ;one also does not want to compose over a retrieval failure (let* ((mapping1 (append (extract-map (first ra1) (second ra1)) ;any variables in the first's retrieval request must be replaced by constants (extract-constant-map (first n1) (first gc2)) ;deal with case were second condition constrains variable in first action (modify1 (first ra1) (second ra1)) (hack (first ga1) (first gc2) (first rc2) (second rc2)))) (rt (retrieval-test mapping1 )) (mapping2 (extend-map (append (modify (first ra1) (second ra1)) (extract-map (first rc2) (second rc2)) ;any variables in the seconds retrieval test but be replaced by constants (extract-map (first gc2) (first n1)) ;any variables in the second's goal test must be replace by ;extending the mapping to include terms in the retrieval buffer (extract-map (first vc2) (first vc1)) (extract-map (first gc2) (first gc1))) ;corresponding terms from the first action or condition mapping1)) ;these substitutions for the second production must be made consistent with the substitution for the first (gc (substitute-vars (car gc1) mapping1)) ;rename terms in first goal test (ga (substitute-vars (car ga1) mapping1)) ;rename terms in first goal change (ma (or (substitute-vars (car ma1) mapping1) (substitute-vars (car ma2) mapping2))) ;whichever has a manual condition (rc (substitute-vars (car rc1) mapping1)) ;maintain the instantiated retrieval test from the first (ra (substitute-vars (car ra2) mapping2)) ;maintain the instantiated retrieval request from the second (vl (or (substitute-vars (car vl1) mapping1)(substitute-vars (car vl2) mapping2))) ; one of the tests of visual location (vla (location-filter (or(substitute-vars (car vla1)mapping1) (substitute-vars (car vla2)mapping2)))) (vs (or (substitute-vars (car vs1) mapping1)(substitute-vars (car vs2) mapping2))) ;one of the visual states (ms (or (substitute-vars (car ms1) mapping1) (substitute-vars (car ms2) mapping2))) (eval (eval-merge rt (substitute-vars eval1 mapping1) (substitute-vars eval2 mapping2))) (vc (or (substitute-vars (car vc1) mapping1) (substitute-vars (car vc2) mapping2))) ;one of the visual object tests (va (or (substitute-vars (car va1) mapping1) (substitute-vars (car va2) mapping2))) ;one of the visual object requests (na (append (list '+goal> (second (car n1))) (cdr (chunk-merge-action (substitute-vars (car ga2) mapping2) ;the goal action must combine the action of the second and the action of (substitute-vars (cons (first (car n1)) (cddr (car n1))) mapping1))))) ;the first giving preference to the second ;NOW THE ACTUAL CALCULATION OF THE NEW PRODUCTION CAN BEGIN (new-condition (remove-nils (list gc rc vl vs vc ms eval) )) ;the new condition with all buffers specified (new-action (remove-nils (list vla va ga ra ma na) )) ;the new action with all buffers specified (production-name (new-name-fct "PRODUCTION")) ;create a new name for the production (production (compile-or-select-production production-name (second (second (first cond-pat1))) ;call code to create production new-condition new-action nil nil))) ;call code to create production (when production (finish-up production (car x) (car y))))) ;when a production was created set its parameters etc ))) ) ;treatment of a third case (defun location-filter (lis) (cond ((atom lis) (chunk-symbol lis)) (t (cons (location-filter (car lis)) (location-filter (cdr lis)))))) (defun modify (ra-pat ra-ins) (if (equal (car (second ra-pat)) 'isa) nil (list (list '=retrieval (car (second ra-ins)))))) (defun modify1 (ra-pat ra-ins) (if (equal (car (second ra-pat)) 'isa) nil (list (list (car (second ra-pat)) (car (second ra-ins)))))) (defun extract-variablesh (lis) (cond ((atom lis) (cond ((and (variablep lis) (not (member lis '(=retrieval =visual =visual-state =goal =visual-location =manual =manual-state =retrieval> =visual> =visual-state> =goal> =visual-location> =manual> =manual-state>)))) (list lis)))) (t (append (extract-variablesh (car lis)) (extract-variablesh (cdr lis)))))) (defun extract-variables (production) (do ((temp (extract-variablesh (production-text (get-safe-production production))) (cdr temp)) (result nil (cond ((member (car temp) (cdr temp)) result) (t (cons (car temp) result))))) ((null temp) result))) (defun renamings (prod1 prod2) (let ((variables1 (extract-variables prod1)) (variables2 (extract-variables prod2))) (do ((temp variables2 (cdr temp)) (result nil (cond ((member (car temp) variables1) (cons (list (car temp) (gentemp (string (car temp)))) result)) (t result)))) ((null temp) result)))) (defun hack (ga1 gc2 rc2p rc2i) (do ((temp (intersection (variables rc2p) (variables gc2)) (cdr temp)) (result nil (cond ((slot-test (car temp) gc2 ga1) (cons (list (slot-test (car temp) gc2 ga1) (find-constant (car temp) rc2p rc2i) ) result)) (t result)))) ((null temp) result))) (defun variables (sexp) (cond ((atom sexp) (cond ((variablep sexp) (list sexp)) (t nil))) (t (append (variables (car sexp)) (variables (cdr sexp)))))) (defun slot-test (variable condition action) (do ((temp (cddr condition) (cdr temp))) ((null temp) nil) (cond ((and (equal (second (car temp)) variable) (assoc (first (car temp)) (cdr action))) (return (second (assoc (first (car temp)) (cdr action)))))))) (defun find-constant (variable pattern ins) (cond ((equal pattern variable) ins) ((atom pattern) nil) (t (or (find-constant variable (car pattern) (car ins)) (find-constant variable (cdr pattern) (cdr ins)))))) (defun retrieval-test (mappings) (let ((critical (assoc '=retrieval mappings))) (cond (critical `(!EVAL! (EQUAL =retrieval (quote ,(chunk-symbol (second critical))))))))) ;attempt to deal with my problem with wmes and symbols ; eliminated due to duplication below ;(defun eval-merge (lis1 lis2 lis3) ; (setf lis1 (eval-filter (second lis1))) ; (setf lis2 (eval-filter (second lis2))) ; (setf lis3 (eval-filter (second lis3))) ; (cond ((and lis1 lis2 lis3) (cons '!eval! (list (list 'and (second lis1) (second lis2) (second lis3))))) ; ((and lis1 lis2) (cons '!eval! (list (list 'and (second lis1) (second lis2) )))) ; ((and lis1 lis3) (cons '!eval! (list (list 'and (second lis1) (second lis3))))) ; ((and lis2 lis3) (cons '!eval! (list (list 'and (second lis2) (second lis3))))) ; ((or lis1 lis2 lis3)))) ;my original-eval merge did not work and so all the rewritten green functions below (defun eval-merge (lis1 lis2 lis3) (let ((result (remove-nils (mapcar 'eval-filter (append (extract-eval lis1) (extract-eval lis2) (extract-eval lis3)))))) (cond ((null result) nil) ((equal (length result) 1) (cons '!eval! result)) (t (list '!eval! (cons 'and result)))))) (defun extract-eval (lis) (cond ((null lis) nil) ((equal (car (second lis)) 'and) (cdr (second lis))) (t (cdr lis)))) ;corrected as an attempt to deal with my problem with wmes and symbols ; eliminated due to duplication below ;(defun eval-filter (lis) ; (setf test (second lis)) ; (if (and (equal (car test) 'equal) (listp (third test))(equal (car (third test)) 'quote) ; (atom (second test))(equal(chunk-symbol (second test)) (second (third test)))) nil lis)) ;attempt to deal with my problem with wmes and symbols (defun eval-filter (lis) (cond ((contains-variable lis) lis) (t nil))) (defun contains-variable (lis) (cond ((atom lis) (variablep lis)) (t (or (contains-variable (car lis)) (contains-variable (cdr lis)))))) ;from Niels (defun compile-or-select-production (name type lhs rhs success failure &optional (documentation nil)) "Either create a new production or select an existing identical one. Return a second value indicating whether it is an existing production (t) or not (nil)." (if (action-test rhs) ;filter cases of a constant assignment (dolist (production (wme-type-productions (get-safe-type type name)) (let ((new-production (compile-production name lhs rhs documentation))) (signal-output *production-compilation-trace* "Compiling Production ~A." new-production) (parameters-fct name (nconc (list :success (if success t nil) :failure (if failure t nil)) *production-compilation-parameters*)) (values new-production nil))) (when (identical-productions (copy-all (cons lhs rhs)) (copy-all (production-text production)) ) ;more of my attempt to deal with my problem with wmes and symbols (signal-output *production-compilation-trace* "Recreating Production ~A." production) ;this change enables the change in finish-up (return production))))) (defun action-test (rhs) (let ((newgoal (assoc '+goal> rhs)) (retrieval (assoc '+retrieval> rhs))) (not (or (and newgoal (not (equal (car (second newgoal)) 'isa)) (not (variablep (car (second newgoal))))) (and retrieval (not (equal (car (second retrieval)) 'isa)) (not (variablep (car (second retrieval))))))))) (defun copy-all (x) (cond ((atom x) (cond ((wmep x) (wme-name x)) (t x))) (t (cons (copy-all (car x)) (copy-all (cdr x)))))) ;largely from Niel -- different definition of new b -- from Niels Thursday (defun finish-up (production old1 old2) ;this code allows us to recognize when we have recreated a production and have yet to try it. In this case it seems ;wise to reset its parameters according to the current parameters of the productions from which it originated. There ;is a more general version of this idea that I did not implement because it is not sorted in the production structure. ;this is that the initialization of the parameters of a production should change whenever the originating productions change but ; the production-specific experience should be allowed to accumulate. (let ((number (no-output (+ (caaar (eval `(spp ,production :successes))) (caaar (eval `(spp ,production :failures))))))) (cond ((<= number *initial-experience*) (cond ((and (equal number 1.0) *production-compilation-trace* *verbose*) (eval `(pp ,production)))) (no-output (let* ((initial-experience *initial-experience*) (effort1 (caar (eval `(spp ,old1 :effort)))) (effort2 (caar (eval `(spp ,old2 :effort)))) (effort (+ effort1 effort2 (- *default-action-time*))) ;we save the 50 ms by having one fewer productions (c2 (caar (eval `(spp ,old2 :c)))) (p2 (caar (eval `(spp ,old2 :p)))) (c1 (caar (eval `(spp ,old1 :c)))) (p1 (caar (eval `(spp ,old1 :p)))) (s1 (caar (eval `(spp ,old1 :success)))) (s2 (caar (eval `(spp ,old2 :success)))) (c (max 0 (+ c1 *cost-penalty*))) ;this is the pessimism built into the productions ;avoid negative costs (p p1) ;since this new production must compete with the first it makes sense to base its parameters on the first. (successes (* p initial-experience)) (failures (- initial-experience successes)) (efforts (* c initial-experience)) (s (or s1 s2)) (pl (first (no-output (sgp :pl))))) (declare (ignore-if-unused p2 c2)) (cond ((eq pl t) (no-output (eval `(spp ,production :effort ,effort :efforts ,efforts :success ,s :successes ,successes :failures ,failures)))) ((null pl) (no-output (eval `(spp ,production :effort ,effort :c ,c :p ,p)))) (t (no-output (eval `(spp ,production :effort ,effort :efforts ,efforts :success ,s :successes ,successes :failures ,failures))))))) (when (and *production-compilation-trace* *verbose*) (eval `(spp ,production))) )))) (defun extend-map (map2 map1) (let (item) (do ((temp map2 (cdr temp)) (result nil (cons (cond ((setf item (assoc (second (car temp)) map1)) (list (caar temp) (second item))) (t (car temp))) result))) ((null temp) (reverse result))))) (defun chunk-merge-condition (chunk1 chunk2) (cond ((not chunk1) chunk2) (t (let ((pair)) (do ((temp1 (cdr chunk1) (cdr temp1)) (result (list (car chunk1)) (cond ((equal (caar temp1) '-) (do ((temp2 (cdr chunk2) (cdr temp2))) ((null temp2) (append result (list (car temp1)))) (cond ((and (equal (first (car temp2)) '-) (equal (second (car temp1)) (second (car temp2))) (equal (third (car temp1)) (third (car temp2)))) (setf chunk2 (remove (car temp2) chunk2)) (return (append result (list (car temp1)))))))) ((setf pair (assoc (caar temp1) (cdr chunk2))) (setf chunk2 (remove pair chunk2)) (append result (list (max-specific (car temp1) pair)))) (t (append result (list (car temp1))))))) ((null temp1) (append result (cdr chunk2)))))))) (defun chunk-merge-action (chunk1 chunk2) (cond ((not chunk1) chunk2) (t (let ((pair)) (do ((temp1 (cdr chunk1) (cdr temp1)) (result (list (car chunk1)) (cond ((equal (caar temp1) '-) (do ((temp2 (cdr chunk2) (cdr temp2))) ((null temp2) (append result (list (car temp1)))) (cond ((and (equal (first (car temp2)) '-) (equal (second (car temp1)) (second (car temp2))) (equal (third (car temp1)) (third (car temp2)))) (setf chunk2 (remove (car temp2) chunk2)) (return (append result (list (car temp1)))))))) ((setf pair (assoc (caar temp1) (cdr chunk2))) (setf chunk2 (remove pair chunk2)) (append result (list (car temp1)))) (t (append result (list (car temp1))))))) ((null temp1) (append result (cdr chunk2)))))))) (defun max-specific (pair1 pair2) (cond ((variablep (second pair2)) pair1) ((variablep (second pair1)) pair2) (t pair1))) (defun remove-nils (lis) (do ((temp lis (cdr temp)) (result nil (cond ((null (car temp)) result) (t (cons (car temp) result))))) ((null temp) (reverse result)))) (defun get-terms (pat ins index) (let ((element (assoc index pat))) (cond (element (list element (nth (position element pat) ins)))))) (defun chunk-dif (condition action) (setf action (cdr action)) (do ((temp2 (cddr condition) (cdr temp2)) (result2 (list (car condition) (cadr condition)) (cond ((assoc (caar temp2) action) result2) ((and (equal (caar temp2) '-) (assoc (cadar temp2) action)) result2) (t (append result2 (list (car temp2))))))) ((null temp2) result2))) (defun extract-map (first second) (cond ((and first second) ;extended test (let (item) (do ((temp (cdr first) (cdr temp)) ;a test has been expanded here to avoid a situation where one does not want the mapping (result (if (and (eq (car first) '=goal>) (eq (car second) '+goal>)) nil (list (list (headere (car first)) (headere (car second))))) (cond ((equal (caar temp) 'isa) result) ((equal (caar temp) '-) (cond ((and (variablep (third (car temp))) (setf item (find-pair (car temp) (cdr second)))) ;extended test (cons (list (third (car temp)) (third item)) result)) (t result))) ((not (variablep (cadar temp))) result) ((and (not (equal (cadar temp) '=goal)) (setf item (assoc (caar temp) (cdr second)))) (cons (list (cadar temp) (cadr item)) result)) (t result)))) ((null temp) result)))))) (defun find-pair (triple lis) (do ((temp lis (cdr temp))) ((null temp) nil) (cond ((and (equal (first triple) (first (car temp))) (equal (second triple) (second (car temp)))) (return (car temp)))))) (defun extract-constant-map (first second) (do ((temp (extract-map first second) (cdr temp)) (result nil (cond ((and (variablep (first (car temp))) (not (variablep (second (car temp))))) (cons (car temp) result)) (t result)))) ((null temp) result))) ;this does the mapping (defun substitute-vars (sexp mapping) (cond ((null sexp) nil) ((atom sexp) (cond ((variable-map sexp mapping)) (t sexp))) (t (cons (substitute-vars (car sexp) mapping) (substitute-vars (cdr sexp) mapping))))) (defun variable-map (term mapping) (cond ((and (variablep term) (not (member term '(=goal> =retrieval>)))) (cond ((headerp term) (setf term (headere term)) (cond ((assoc term mapping) (header-fn (cadr (assoc term mapping)))))) ((assoc term mapping) (cadr (assoc term mapping))))))) ;Utilities (defun get-condition (a) (first (production-text (get-safe-production (car a))))) (defun get-action (a) (cdr (production-text (get-safe-production (car a))))) (defun headerp (term) (let ((string (string-sym term))) (equal (aref string (1- (length string))) #\>))) (defun headere (term) (let* ((string (string-sym term)) (bound (1- (length string))) (ans (make-string bound))) (do ((count 0 (1+ count))) ((equal count bound) (let ((r (intern ans))) ;this again deals with situations where merge has changed chunk names (cond ((and *merge* (equal r (wme-name (first *merge*)))) (first *merge*)) ((and (not (variablep r)) (not (equal r '+goal)) (not (equal r '+retrieval))) (get-safe-wme r)) (t r)))) (setf (aref ans count) (aref string count))))) (defun string-sym (item) (cond ((symbolp item) (string1 item)) (t (string1 (car (no-output (eval `(wm ,item)))))))) (defun string1 (item) (cond ((numberp item) (prin1-to-string item)) (t (string item)))) (defun header-fn (x) (intern (merge 'string (cond ((numberp x) (format nil "~d" x)) ((symbolp x) (string x)) (t (string (car (no-output (eval `(wm ,x)))))) ) ">" 'eq))) (defun chunk-symbol (x) (if (wmep x) (get-wme-name x) x)) ;;; lhs (defmacro set-bindings (slots actions) "Sets the stack entries to the slots values." `(dolist (action ,actions t) (unless (setf (instantiation-variable *instantiation* (action-value action)) (slots-slot ,slots (action-slot action))) (signal-output *exact-matching-trace* "Variable in slot ~S cannot be bound to nil: binding fails." (action-name action)) (return nil)))) (defmacro interpret-value (action) "Interpret a slot value description in a number of ways." `(case (action-dispatch ,action) (:literal (action-value ,action)) (:stack (instantiation-variable *instantiation* (action-value ,action))) (:eval (get-wme-or-constant (funcall (action-value ,action)))))) (defmacro test-slots-buffer (wme slots actions) "Tests that slots satisfies all the actions. Specifically for buffers such as goal, retrieval and p/m buffers that do not require activation computations." `(progn (signal-output *exact-matching-trace* "Matching CHUNK ~S." ,wme) (dolist (action ,actions t) (let ((actual (slots-slot ,slots (action-slot action))) (desired (interpret-value action)) (negation (action-negation action)) (slot (action-name action))) (cond ((null negation) (unless (equal actual desired) (signal-output *exact-matching-trace* "Value ~S is different from condition ~S in slot ~S: test fails." actual desired slot) (return nil))) ((eq negation '-) (when (equal actual desired) (signal-output *exact-matching-trace* "Value ~S is equal to condition ~S in slot ~S: negation test fails." actual desired slot) (return nil))) ((fboundp negation) (unless (and (numberp actual) (numberp desired) (funcall negation actual desired)) (signal-output *exact-matching-trace* "Value ~S does not match test ~S with condition ~S in slot ~S: test fails." actual negation desired slot) (return nil))) (t (signal-output *exact-matching-trace* "Unknown test ~S in slot ~S: ignoring it." negation slot))))))) (defmacro test-slots (wme slots actions) ; &optional index) "Tests that slots satisfies all the actions. Also tests that activation is above threshold when applicable. Equal test necessary to handle the full potential range of values." `(progn (signal-output *exact-matching-trace* "Matching CHUNK ~S." ,wme) (dolist (action ,actions (if *retrieval-threshold* (activation ,wme) t)) (let ((actual (slots-slot ,slots (action-slot action))) (desired (interpret-value action)) (negation (action-negation action)) (slot (action-name action))) (cond ((null negation) (unless (equal actual desired) (signal-output *exact-matching-trace* "Value ~S is different from condition ~S in slot ~S: test fails." actual desired slot) (return nil))) ((eq negation '-) (when (equal actual desired) (signal-output *exact-matching-trace* "Value ~S is equal to condition ~S in slot ~S: negation test fails." actual desired slot) (return nil))) ((fboundp negation) (unless (and (numberp actual) (numberp desired) (funcall negation actual desired)) (signal-output *exact-matching-trace* "Value ~S does not match test ~S with condition ~S in slot ~S: test fails." actual negation desired slot) (return nil))) (t (signal-output *exact-matching-trace* "Unknown test ~S in slot ~S: ignoring it." negation slot))))))) (defun add-instantiation-to-conflict-set (arguments) "Adds *instantiation* to conflict set, sorted by value or PG-C. No need to compute latencies." (declare (ignore arguments)) (unless *enable-rational-analysis* (setf (instantiation-gain *instantiation*) (get-functional-parameter (production-value (instantiation-production *instantiation*)))) (when *exp-gain-noise* (incf (instantiation-gain *instantiation*) (noise *exp-gain-noise*)))) (let ((rest-cset *conflict-set*) (item nil) (index 0) (equals 0)) (loop (setf item (pop rest-cset)) (when (or (null item) (> (instantiation-gain *instantiation*) (instantiation-gain item))) (return)) (if (= (instantiation-gain *instantiation*) (instantiation-gain item)) (incf equals 1) (incf index 1))) (when (> equals 0) (incf index (if *enable-randomness* (random (+ equals 1)) equals))) (if (= index 0) (push *instantiation* *conflict-set*) (let ((previous (nthcdr (- index 1) *conflict-set*))) (push *instantiation* (cdr previous)))))) (defmacro add-latency (wme activation) `(let ((latency (activation-latency ,activation))) (signal-output *activation-trace* "CHUNK ~S Activation ~6,3F Latency ~6,3F" ,wme ,activation latency) (incf *latency* latency))) (defun direct-test-and-bind-buffer (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. Returns t if a complete match is found, nil otherwise. Specific to fixed buffers such as goal, retrieval, and p/m buffers." (let* ((wme-index (pop arguments)) (wme (instantiation-variable *instantiation* wme-index)) (wmetype (pop arguments)) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (if (and (wmep wme) (subtype (wme-type wme) wmetype)) (let ((slots (wme-slots wme))) ;; Set bindings before testing slots (and (set-bindings slots slots-bound) (test-slots-buffer wme slots slots-tested) (simulate-call arguments))) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype)))) (defun direct-test-and-bind (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. Returns t if a complete match is found, nil otherwise." (let* ((wme-index (pop arguments)) (wme (instantiation-variable *instantiation* wme-index)) (wmetype (or (pop arguments) (when (wmep wme) (wme-type wme)))) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (if (and (wmep wme) (subtype (wme-type wme) wmetype)) (let ((slots (wme-slots wme))) ;; Set bindings before testing slots (and (set-bindings slots slots-bound) (test-slots wme slots slots-tested) ; wme-index) (simulate-call arguments))) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype)))) (defun indirect-test-and-bind (arguments) "Implements an indirect match. For all subtypes of the wme, try all the wmes and test their slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. Returns t if a complete match is found, nil otherwise." (let* ((wme-index (pop arguments)) (wmetype (pop arguments)) (slots nil) (slots-tested (pop arguments)) (slots-bound (pop arguments)) (retrieval-list nil)) (signal-output *exact-matching-trace* "Matching CHUNKs of type ~S." wmetype) ;; Return the winning chunk or nil to handle-failure ;; If *enable-randomness* is on, then pick randomly otherwise pick the first (dolist (type (wme-type-subtypes wmetype) (setf *retrieval-scheduler* (if *enable-randomness* (nth (random (length retrieval-list)) retrieval-list) (first retrieval-list)))) (dolist (wme (wme-type-wmes type)) (setf slots (wme-slots wme)) (if wme-index (setf (instantiation-variable *instantiation* wme-index) wme) (setf *retrieval-scheduler* wme)) ;; Set bindings before testing slots (when (and (set-bindings slots slots-bound) (test-slots wme slots slots-tested) (simulate-call arguments)) ;; In action retrieval mode, keep the list of retrievals (unless wme-index (push-last *retrieval-scheduler* retrieval-list)) ; pick and initialize a new instantiation (let* ((production (instantiation-production *instantiation*)) (next-instantiation (get-next-instantiation production)) (index 1) (size (production-size production))) (loop (when (= index size) (return)) (setf (instantiation-variable next-instantiation index) (instantiation-variable *instantiation* index)) (incf index 1)) (setf *instantiation* next-instantiation))))))) (defun eval-test (arguments) "Evaluate the first of the arguments, then call the next clause if true." (let ((expr (pop arguments))) (if (funcall expr) (simulate-call arguments) (signal-output *exact-matching-trace* "Expression ~S evaluated to NIL." expr)))) (defun bind-lhs (arguments) "Binds the first argument to the evaluation of the second, then call the next clause if true." (let ((index (pop arguments)) (expr (pop arguments))) (if (setf (instantiation-variable *instantiation* index) (get-wme-or-constant (funcall expr))) (simulate-call arguments) (signal-output *exact-matching-trace* "Expression ~S evaluated to NIL." expr)))) (defun bind-find-location (arguments) "Binds the first of the arguments to the value of find-location applied to the second." (let ((index (pop arguments)) (expr (pop arguments))) (if (setf (instantiation-variable *instantiation* index) (get-wme-or-constant (apply #'find-location (funcall expr)))) (simulate-call arguments) (signal-output *exact-matching-trace* "Find-location of ~S returned NIL." expr)))) ;;; Partial Matching (LHS) (defun similarity-fct (wmej wmei) "Retrieves the similarity between wmej and wmei, in that order." (let ((*verbose* t) (*goal-depth* 1) (similarity 0.0)) (setf wmej (or (get-wme wmej) wmej)) (setf wmei (or (get-wme wmei) wmei)) (setf similarity (get-similarity wmej wmei)) (signal-output *command-trace* "~6,3F" similarity) similarity)) (defun set-similarities-fct (triplets) "Sets the similarities between the list of triplets, both ways." (let ((similarities nil)) (dolist (triplet triplets similarities) (let ((wmej (get-safe-wme (first triplet))) (wmei (get-safe-wme (second triplet))) (similarity (third triplet))) (when (and wmej wmei) (push-last similarity similarities) (set-similarity wmej wmei similarity) (set-similarity wmei wmej similarity)))))) (defmacro test-slots-pm (wme slots actions) "Tests that wme slots satisfy all the actions. Equal test necessary to handle the full potential range of non-wme values. Partial matching on wme values. Returns the activation of wme if above threshold, nil otherwise." `(let ((activation (activation ,wme))) (signal-output *partial-matching-trace* "Partial matching chunk ~S with activation ~6,3F" ,wme activation) (dolist (action ,actions (progn (signal-output *partial-matching-trace* "Matching score of chunk ~S is ~6,3F." ,wme activation) activation)) (let* ((value (slots-slot ,slots (action-slot action))) (target (interpret-value action)) (similarity (get-similarity value target)) (negation (action-negation action))) (signal-output *partial-matching-trace* "Similarity between chunks ~S and ~S is ~6,3F" value target similarity) (cond ((null negation) (if (numberp similarity) (let ((match (* similarity *mismatch-penalty*))) (incf activation match) (signal-output *partial-matching-trace* "Adjusting activation by ~6,3F to ~6,3F" match activation)) (unless similarity (signal-output *partial-matching-trace* "Non-chunk mismatch between ~S and ~S: failure." value target) (return nil)))) ((eq negation '-) (if (numberp similarity) (when (= similarity *max-sim*) (let ((match (* similarity *max-dif*))) (incf activation match) (signal-output *partial-matching-trace* "Adjusting activation by ~6,3F to ~6,3F" match activation))) (when similarity (signal-output *partial-matching-trace* "Non-chunk match between ~S and ~S for negation test: failure." value target) (return nil)))) ((fboundp negation) (unless (and (numberp value) (numberp target) (funcall negation value target)) (signal-output *exact-matching-trace* "Value ~S does not match test ~S with condition ~S: test fails." value negation target) (return nil))) (t (signal-output *exact-matching-trace* "Unknown test ~S: ignoring it." negation))))))) (defun direct-test-and-bind-pm (arguments) "Implements a direct match. Tests for type and then slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. This is the rational analysis version, which handles partial matching too." (let* ((wme-index (pop arguments)) (wme (instantiation-variable *instantiation* wme-index)) (wmetype (or (pop arguments) (when (wmep wme) (wme-type wme)))) (slots-tested (pop arguments)) (slots-bound (pop arguments))) (cond ((and (wmep wme) (subtype (wme-type wme) wmetype)) (let* ((slots (wme-slots wme)) ;; Set bindings before testing slots (activation (and (set-bindings slots slots-bound) (if *partial-matching* (test-slots-pm wme slots slots-tested) (test-slots wme slots slots-tested))))) (setf *sum-exp-act* (exp (/ *retrieval-threshold* *temperature*))) ;; In exact match, only counts the chunk activation if it matches? (when activation (incf *sum-exp-act* (exp (/ activation *temperature*)))) ;; If activation is below threshold, then reject it (when (and activation (< activation *retrieval-threshold*)) (if *partial-matching* (signal-output *partial-matching-trace* "CHUNK ~S match score ~6,3F is below threshold ~6,3F: failure." wme activation *retrieval-threshold*) (signal-output *exact-matching-trace* "CHUNK ~S activation ~6,3F is below threshold ~6,3F: failure." wme activation *retrieval-threshold*)) (setf activation nil)) (add-latency wme (or activation *retrieval-threshold*)) (when activation (simulate-call arguments)))) (t (setf *sum-exp-act* (exp (/ *retrieval-threshold* *temperature*))) (add-latency wme *retrieval-threshold*) (signal-output *exact-matching-trace* "~S is not a CHUNK of type ~S." wme wmetype))))) (defmacro set-blending-bindings (chunk-probs actions total-prob) "Sets the stack entries to the slots values and returns the lowest activation of the matches, on the assumption that it determines retrieval." `(let ((worst-activation nil)) ;; iterate separately for each value binding (dolist (action ,actions worst-activation) (let ((value-probs nil) (value nil) (probability nil) (value-type nil) (value-list nil) (best-value nil) (best-sum nil)) ;; get the value and probability for each chunk-probability pair (dolist (chunk-prob ,chunk-probs) (setf value (wme-slot (car chunk-prob) (action-slot action))) (setf probability (/ (cdr chunk-prob) ;; just in case total-prob is 0 (unlikely) (if (zerop ,total-prob) 1.0 ,total-prob))) (push (cons value probability) value-probs) (signal-output *blending-trace* "Chunk ~S Value ~S Probability ~6,3F" (car chunk-prob) value probability) ;; if the value is nil, then do nothing ;; if the value is a chunk, then note the type (t means conflict) ;; if the value is a number, then add it to the list and ;; note whether it is integer or float ;; otherwise add it to the list if it isn't already numbers (cond ((null value)) ((wmep value) (let ((type (wme-type value))) (if (null value-type) (setf value-type type) (unless (equal value-type type) (setf value-type t))))) ((numberp value) (let ((type (if (integerp value) 'integer 'float))) (cond ((null value-list) (setf value-list (list type))) ((eq (first value-list) 'integer) (when (eq type 'float) (setf (first value-list) 'float))) ((eq (first value-list) 'float)) (t (setf value-list (list type)))) (push-last (cons value probability) value-list))) (t (unless (or (eq (first value-list) 'integer) (eq (first value-list) 'float)) (push (cons value probability) value-list))))) ;; if all values are of the same chunk type, then look at all chunks ;; otherwise only consider the chunks from the given type ;; if some non-chunks values were present, then add them ;; unless they were numbers, which then take precedence (cond ((eq (first value-list) 'integer) (setf best-value (round (funcall *blending-hook-fn* (rest value-list))))) ((eq (first value-list) 'float) (setf best-value (funcall *blending-hook-fn* (rest value-list)))) (t (if (eq value-type t) (for-all-wmes wme (push wme value-list)) (unless (null value-type) (dolist (wme (wme-type-wmes value-type)) (push wme value-list)))) ;; compute the fit for all possible values and find the best (dolist (value value-list) (let ((sum 0.0)) (dolist (value-prob value-probs) (let ((penalty (- 1.0 (or (get-similarity (car value-prob) value) 0.0)))) (incf sum (* (cdr value-prob) penalty penalty)))) (signal-output *blending-trace* "Value ~S Error ~6,3F" value sum) (unless (and best-value (> sum best-sum)) (setf best-value value) (setf best-sum sum)))))) ;; compute the activation of the match given the best value (when best-value (let ((activation (funcall *blending-activation-fn* best-value value-probs ,total-prob))) (signal-output *blending-trace* "Best Value ~S Activation ~6,3F" best-value activation) ;; if multiple values are bound, each process is independent ;; and the slowest one determines the latency (when (or (null worst-activation) (< activation worst-activation)) (setf worst-activation activation)))) ;; bind the best value if available or fail and return (unless (setf (instantiation-variable *instantiation* (action-value action)) best-value) (signal-output *exact-matching-trace* "Variable in slot ~S cannot be bound to nil: binding fails." (action-name action)) (return nil)))))) (defun indirect-test-and-bind-pm (arguments) "Implements an indirect match. For all subtypes of the wme, try all the wmes and test their slots, then bind variables. Finally, call the function implementing the next clause. Arguments is a list of the wme's stack index, its type, a list of slots tests and bindings, and the next call if any. This is the rational analysis version, which handles partial matching too. In addition, if blending is enabled then bind the consensus values." (let* ((wme-index (pop arguments)) (wmetype (pop arguments)) (slots-tested (pop arguments)) (slots-bound (pop arguments)) (best-activation (if *blending* most-negative-short-float *retrieval-threshold*)) (best-wme nil) (slots nil) (activation nil) (temperature (when *blending* (or *temperature* (* (sqrt 2.0) *activation-noise*)))) (total-prob (when *blending* (exp (/ *retrieval-threshold* temperature)))) (chunk-probs nil)) (signal-output *partial-matching-trace* "Matching CHUNKs of type ~S." wmetype) (setf *sum-exp-act* (exp (/ *retrieval-threshold* *temperature*))) ;; If blending is on, then do not apply retrieval threshold to individual chunks (let ((*retrieval-threshold* best-activation)) (dolist (type (wme-type-subtypes wmetype)) (dolist (wme (wme-type-wmes type)) (setf slots (wme-slots wme)) (if wme-index (setf (instantiation-variable *instantiation* wme-index) wme) (setf *retrieval-scheduler* wme)) ;; Set bindings before testing slots (setf activation (and (set-bindings slots slots-bound) (if *partial-matching* (test-slots-pm wme slots slots-tested) (test-slots wme slots slots-tested)))) ;; In exact match, only counts the chunk activation if it matches? (when activation (incf *sum-exp-act* (exp (/ activation *temperature*)))) (when (and activation *blending*) (let ((prob (exp (/ activation temperature)))) (push (cons wme prob) chunk-probs) (incf total-prob prob))) (when (and activation (>= activation best-activation)) (signal-output *partial-matching-trace* "Activation ~6,3F is larger than previous best ~6,3F: selecting ~S." activation best-activation wme) (setf best-activation activation) (setf best-wme wme))))) (if (or (eq *blending* t) (and (eq *blending* 'rt) (< best-activation *retrieval-threshold*))) (setf best-activation (set-blending-bindings chunk-probs slots-bound total-prob)) (setf chunk-probs nil)) (unless (or (eq *blending* 'rt) (>= best-activation *retrieval-threshold*)) (setf best-activation *retrieval-threshold*) (setf best-wme nil)) (add-latency (or best-wme 'failure) best-activation) (cond (best-wme (setf slots (wme-slots best-wme)) (if wme-index (setf (instantiation-variable *instantiation* wme-index) best-wme) (setf *retrieval-scheduler* best-wme)) (unless chunk-probs (set-bindings slots slots-bound)) (simulate-call arguments)) (t (signal-output *partial-matching-trace* "No chunk reached activation threshold ~6,3F: matching fails." *retrieval-threshold*))))) ;;; rhs (defmacro set-wmfocus (&key (wme nil) (spread nil) (activation-sources nil)) "Sets the focus to wme, and update activation spread and goal sources, if necessary." `(progn (setf *wmfocus* ,wme) (signal-output *goal-trace* "Switching to goal ~S." *wmfocus*) (when *enable-rational-analysis* ,(when spread `(update-activation-spread))) (setf *goal-sources* (or ,activation-sources (when ,wme (copy-list (rest (wme-slot-wmes ,wme)))))) *wmfocus*)) (defmacro reset-frame (stack-frame &key (spread nil)) "Resets a frame of the goal stack." `(let ((frame ,stack-frame)) (set-wmfocus :wme (goal-frame-focus frame) :spread ,spread :activation-sources (goal-frame-sources frame)))) (defmacro return-values () "Return values from the focus using the top goal frame." `(let ((wme nil) (slot nil) (value nil)) (dolist (return (goal-frame-return-values (first *goal-stack*))) (setf value (wme-slot *wmfocus* (first return))) (dolist (destination (rest return)) (setf wme (goal-frame-focus (nth (car destination) *goal-stack*))) (setf slot (cdr destination)) (set-slot-value wme slot value))))) (defun rehearse-chunk-fct (chunks &key (repeat 1) (force nil) (cycle nil) (time nil)) "Rehearses chunks repeat times. If a chunk is a list, then the first element of the list is the chunk to rehearse and the rest is the list of sources. When force is on, update the statistics whether or not learning is on. When cycle and/or time is set, increment the equivalent ACT-R counters by those amounts (1 by default)." (let ((structures nil) (level (* 1.0 repeat))) (unless (integerp repeat) (setf repeat (round repeat))) (dolist (spec chunks structures) (let ((chunk (get-safe-wme (if (listp spec) (first spec) spec)))) (when chunk (push-last spec structures) (when (or force *base-level-learning*) ;; call add-reference because generalization needs to be implemented properly (add-reference (wme-references chunk) *optimized-learning* repeat *time*)) (when (or force *associative-learning*) (let ((sources nil)) (if (listp spec) (dolist (source (rest spec)) (setf source (get-safe-wme source)) (when source (push source sources))) ; add the chunk itself to the set of sources (setf sources (cons chunk (rest (wme-slot-wmes chunk))))) ;; Do not parcel out the sources. (incf (wme-needed chunk) level) (dolist (source sources) (incf (wme-contexts source) level) (incf (ia-fnicj (get-make-ia source chunk)) level)))) (when cycle (incf *cycle* (* repeat (if (numberp cycle) cycle 1)))) (when time (incf *time* (* level (if (numberp time) time 1.0))))))))) (defun identical-wme (&optional (goal *wmfocus*)) "If a wme identical to goal exists, then deletes goal and returns the identical wme after rehearsal. Otherwise, returns goal." (when goal (let ((type (wme-type goal))) (setf goal (dolist (wme (wme-type-wmes type) goal) (when (and (not (eq goal wme)) (dolist (slot (wme-type-slots type) t) (unless (equal (wme-slot goal (slot-index slot)) (wme-slot wme (slot-index slot))) (return nil)))) ; delete goal and replace any remaining reference with wme (delete-wme goal wme) ; FIX: rehearse IAs as well as BLLs (rehearse-chunk-fct (list (cons wme *goal-sources*))) (return wme)))) ;;; If of type dependency, then call solve-by-analogy ;;; FIX: only if it has not been popped in failure (when (and (eq (wme-type-name type) 'dependency) *instantiation* (not (get-functional-parameter (production-failure (instantiation-production *instantiation*))))) (solve-by-analogy goal)) goal))) (defun goal-focus-fct (&optional (wmes nil)) "Sets the focus to the first wme, the list to focus-list, or print the current focus if none is selected." (let ((*verbose* t) (*goal-depth* 1) (wme nil)) (cond (wmes (setf wme (get-safe-wme (first wmes))) (when wme (identical-wme) (set-wmfocus :wme wme :spread t)) (setf *wmfocus-list* (list *wmfocus*)) (dolist (wme-name (rest wmes)) (setf wme (get-safe-wme wme-name)) (when wme (push-last wme *wmfocus-list*)))) (*wmfocus* (pprint-wme *wmfocus*)) (t (signal-output *command-trace* "No focus currently selected."))) (when *wmfocus* (wme-name *wmfocus*)))) (defmacro focus-macro (goal &optional return-values) "Replaces the current focus with the new goal. Return values from the old goal and install the return value specs for the new one in its place." `(progn (save-state-change :focus-on (make-goal-frame :focus *wmfocus* :return-values (when *goal-stack* (goal-frame-return-values (first *goal-stack*))) :sources *goal-sources*)) (when *goal-stack* (return-values) (setf (goal-frame-return-values (first *goal-stack*)) ,return-values)) (identical-wme) (set-wmfocus :wme ,goal))) (defun focus-on-goal-fct (wme) "Replaces the current focus with the new goal. Return values from the old goal and install the return value specs for the new one in its place." (let ((subgoal (get-safe-wme wme))) (when subgoal (focus-macro subgoal) (wme-name wme)))) (defun focus-fct (arguments) "Replaces the current focus with the new goal. Return values from the old goal and install the return value specs for the new one in its place." (let* ((subgoal (pop arguments)) (return-values (pop arguments))) (focus-macro (instantiation-variable *instantiation* subgoal) return-values))) (defun buffers-fct (buffers) "Displays and sets the contents of buffers." (let ((buffer-values nil)) (cond ((null buffers) (dolist (buffer *buffer-keywords*) (let ((name (first buffer)) (value (rest buffer))) (unless (variablep name) (setf value (symbol-value value)) (push-last (when value (wme-name value)) buffer-values) (format t "~S~C~S~%" name #\tab value))))) (t (loop (unless buffers (return)) (let* ((buffer (pop buffers)) (name (assoc buffer *buffer-keywords*))) (cond (name (unless (or (null buffers) (and (assoc (first buffers) *buffer-keywords*) (not (equal (first name) (first buffers))))) (let ((value (when (first buffers) (get-safe-wme (first buffers))))) (if (or (eq buffer 'goal) (eq buffer '=goal)) (focus-macro value) (setf (symbol-value (rest name)) value))) (pop buffers)) (let ((value (symbol-value (rest name)))) (push-last (when value (wme-name value)) buffer-values))) (t (signal-warn "UNKNOWN BUFFER ~S" buffer))))))) buffer-values)) (defun retrieval-fct (&optional (chunk nil)) "Displays or changes (if argument is supplied) the value of the last chunk retrieved." (cond (chunk (setf chunk (get-safe-wme chunk)) (setf *retrieval* chunk)) (*retrieval* (pprint-wme *retrieval*)) (t (signal-output *command-trace* "No retrieval currently present."))) (when *retrieval* (wme-name *retrieval*))) (defmacro push-macro (goal &optional return-values) "Pushes the current goal on the stack and install goal as the new focus, with optional return values." `(progn (save-state-change :push-goal) (when *wmfocus* (push (make-goal-frame :focus *wmfocus* :return-values ,return-values :sources *goal-sources*) *goal-stack*) (incf *goal-depth* 3)) (set-wmfocus :wme ,goal))) (defun push-goal-fct (wme) "Pushes the current goal on the stack and install wme as the new focus." (let ((subgoal (get-safe-wme wme))) (when subgoal (push-macro subgoal) (wme-name subgoal)))) (defun push-fct (arguments) "Pushes the current goal on the stack and install the first argument as the new focus." (let* ((subgoal (pop arguments)) (return-values (pop arguments))) (push-macro (instantiation-variable *instantiation* subgoal) return-values))) (defmacro pop-macro () "Pops the top goal off the stack and into the focus and return values." `(progn (save-state-change :pop-goal (make-goal-frame :focus *wmfocus* :return-values (when *goal-stack* (goal-frame-return-values (first *goal-stack*))) :sources *goal-sources*)) (cond (*goal-stack* (return-values) (decf *goal-depth* 3) (identical-wme) (reset-frame (pop *goal-stack*))) (t (identical-wme) (set-wmfocus))))) (defun pop-goal-fct () "Pops the top goal off the stack and into the focus and return values." (pop-macro) (when *wmfocus* (wme-name *wmfocus*))) (defun pop-fct (arguments) "Pops the top goal off the stack and into the focus and return values." (declare (ignore arguments)) (pop-macro)) (defun clear-goal-stack-fct () "Clears the goal stack by restoring the top goal." (loop (unless *goal-stack* (return)) (pop-macro)) (when *wmfocus* (wme-name *wmfocus*))) (defun goal-stack-fct () "Prints the list of wmes on the goal stack." (let ((*verbose* t) (*goal-depth* 1) (goals nil)) (signal-output *command-trace* "Focus: ~S ~6,3F" *wmfocus* *g*) (when *wmfocus* (push (wme-name *wmfocus*) goals)) (dolist (frame *goal-stack*) (push-last (wme-name (goal-frame-focus frame)) goals) (signal-output *command-trace* "~S" (goal-frame-focus frame))) goals)) (defmacro set-slots (wme actions) "Sets wme slots according to actions." `(dolist (action ,actions) (set-slot-value ,wme (action-slot action) (interpret-value action)))) (defun modify-old-wme (arguments) "Set new slot values of an existing wme." (let* ((wme (instantiation-variable *instantiation* (pop arguments))) (type (pop arguments))) (signal-output *dm-trace* "Modifying CHUNK ~S." wme) (if (subtype (wme-type wme) type) (set-slots wme (pop arguments)) (signal-warn "CHUNK ~S IS NOT OF TYPE ~S." wme type)))) (defun create-new-wme (arguments) "Creates a new wme, sets its slot values and sets its stack binding." (let* ((wme-index (pop arguments)) (wme-name (safe-gentemp (symbol-name (pop arguments)))) (wme-type (pop arguments)) (wme (create-wme wme-name wme-type))) (signal-output *dm-trace* "Creating CHUNK ~A." wme-name) (setf (instantiation-variable *instantiation* wme-index) wme) (set-slots wme (pop arguments)))) (defun output (arguments) "Prints the first argument, which is interpreted as a format command." (let* ((format-args (pop arguments)) (format-string (first format-args)) (format-list (list *goal-depth*))) (when (and *verbose* *output-trace*) (dolist (index (rest format-args)) (push-last (if (functionp index) (funcall index) (instantiation-variable *instantiation* index)) format-list)) (format *output-trace* "~?" format-string format-list)))) (defun eval-side (arguments) "Evaluate the first of the arguments (for the side-effects), then call the next clause." (funcall (pop arguments))) (defun bind-rhs (arguments) "Binds the first argument to the evaluation of the second." (setf (instantiation-variable *instantiation* (pop arguments)) (get-wme-or-constant (funcall (pop arguments))))) (defun bind-move-attention (arguments) "Binds the first of the arguments to the value of move-location applied to the second." (setf (instantiation-variable *instantiation* (pop arguments)) (get-wme-or-constant (apply #'move-attention (funcall (pop arguments)))))) (defun visual-action (arguments) "Calls the visual routine with proper evaluation of arguments." (apply (pop arguments) (funcall (pop arguments)))) (defun action-command (arguments) "Calls the send-command routine with the cycle time and rest of arguments. Was later modified to send-RPM-command." (apply 'send-rpm-command (- *time* *start-time*) (funcall (pop arguments)))) (defun delete-wme-variable (arguments) "Delete the wme indexed by the first argument." (let ((wme (svref *instantiation* (first arguments)))) (signal-output *dm-trace* "Deleting CHUNK ~S." wme) (delete-wme wme))) (defun copy-chunk-variable (arguments) "Binds the first argument to a list of the copies of the second." (setf (instantiation-variable *instantiation* (pop arguments)) (copy-chunks (funcall (pop arguments))))) (defun stop (arguments) "Stops the run after this cycle." (declare (ignore arguments)) (setf *stop* t)) (defun restart-top-goal (arguments) "Pops all goals on the stack." (loop (unless *goal-stack* (return)) (pop-fct arguments))) ;;; Run (defmacro generate-all-instantiations () "Do left-hand side matching of all productions on the current WM state." `(progn (setf *conflict-set* nil) ; (if *wmfocus* (dolist (goal-type (cons (get-safe-type t 'generate-all-instantiations) (when *wmfocus* (wme-type-supertypes (wme-type *wmfocus*)))) *conflict-set*) (dolist (production (wme-type-productions goal-type)) (match-lhs production))))) ; (signal-warn "NO GOAL SELECTED AS FOCUS.")))) (defun pmatches-fct () "Generates and prints instantiations for the current WM state. Does NOT guarantee that Expected Gain Noise is going to be the same in run." (let ((*verbose* t) (*goal-depth* 1) (productions nil)) (pprint-instantiations (generate-all-instantiations) *command-trace* *matches-trace*) (dolist (instantiation *conflict-set* productions) (push-last (production-name (instantiation-production instantiation)) productions)))) (defun whynot-fct (productions) "Tries to match production(s) name with exact matching trace on. The whole lhs is matched, even when ERA is on." (let ((*exact-matching-trace* *command-trace*) (*partial-matching-trace* *command-trace*) (*verbose* t) (*goal-depth* 1)) (when productions (setf *conflict-set* nil) (dolist (production productions) (setf production (get-safe-production production)) (when production (when (and (match-lhs production) *enable-rational-analysis*) (let ((*instantiation* (production-instantiation production))) (unless (simulate-call (cdr (production-lhs production))) (setf *conflict-set* (delete *instantiation* *conflict-set* :test #'eq)))))))) (setf productions nil) (pprint-instantiations *conflict-set* *command-trace*) (dolist (instantiation *conflict-set* productions) (push-last (production-name (instantiation-production instantiation)) productions)))) (defun choose-instantiation () "If rational analysis is enabled, then pick the best instantiation. Otherwise, pick the first one since it is the one with the highest value. Returns the winning instantiation, the latency of the matching process, and the latency of the productions which failed before the winning one." (let ((index 0) (failed-latencies 0.0)) (setf *instantiation* nil) (setf *latency* 0.0) (cond ((and (null *conflict-set*) *retrieval-threshold*) (setf *latency* (activation-latency *retrieval-threshold*))) (*enable-rational-analysis* (signal-output *activation-trace* "Sources of activation are: ~S" *activation-sources*) (dolist (instantiation *conflict-set* (setf *instantiation* nil)) (let* ((production (instantiation-production instantiation)) (lhs (cdr (production-lhs production)))) (signal-output (or *exact-matching-trace* *partial-matching-trace* *conflict-resolution-trace*) "Matching production ~S" production) (signal-output *activation-trace* "Adding latency of production ~A" production) (setf *instantiation* instantiation) (incf index 1) (when (simulate-call lhs) (return)) ;;; if matching fails, learn the failure with the latency of the failed matching (when *parameters-learning* (learn-parameters nil nil :latency (- *latency* failed-latencies))) (signal-output *conflict-resolution-trace* "Failed production ~A" production) (signal-output *conflict-resolution-trace* "Matching Latency: ~6,3F" (- *latency* failed-latencies)) (setf failed-latencies *latency*))) (signal-output *conflict-set-trace* "~D productions out of ~D considered; expected gain of chosen is: ~6,3F" index (length *conflict-set*) (when *instantiation* (instantiation-gain *instantiation*)))) (t (signal-output *conflict-set-trace* "~D instantiations in the conflict set" (length *conflict-set*)) (setf *instantiation* (first *conflict-set*)))) (when (and *instantiation* *production-trace*) (pprint-instantiation *instantiation* *production-trace*)) (values *instantiation* *latency* failed-latencies))) (defun matching-cycle (&optional (cycles nil) (retrieval-threshold *retrieval-threshold*) (utility-threshold *utility-threshold*)) "Implements the production matching (lhs) cycle called by run-fct and the PM scheduler. Cycles is the time limit for the matching cycle if applicable. Returns the latency of the action phase if any, nil otherwise." (let ((matching-latency 0.0) (failed-latencies 0.0) (action nil)) (when (and *retrieval-threshold* *activation-noise*) (let ((noise (noise *activation-noise*))) (setf *retrieval-threshold* (+ retrieval-threshold noise)) (signal-output *activation-trace* " Adding noise ~6,3F to retrieval threshold for a total of ~6,3F" noise *retrieval-threshold*))) (when (and *utility-threshold* *exp-gain-noise*) (let ((noise (noise *exp-gain-noise*))) (setf *utility-threshold* (+ utility-threshold noise)) (signal-output (or *exact-matching-trace* *partial-matching-trace*) " Adding noise ~6,3F to utility threshold for a total of ~6,3F" noise *utility-threshold*))) (generate-all-instantiations) (when *matches-trace* (pprint-instantiations *conflict-set* *matches-trace* *matches-trace*)) (setf *instantiation* nil) (when *conflict-set-hook-fn* (multiple-value-bind (instantiations latency) (funcall *conflict-set-hook-fn* *conflict-set*) (declare (ignore latency)) (when instantiations ; if returns nil, then continue unchanged (cond ((listp instantiations) ; if returns a list, then interpret as new cset (setf *conflict-set* instantiations)) (t ; otherwise, restrict the conflict set to selected instantiation (setf *conflict-set* (list instantiations))))))) (unless *instantiation* (multiple-value-setq (*instantiation* matching-latency failed-latencies) (choose-instantiation))) ;;; the following used to be part of the action cycle but really belongs in matching (cond (*instantiation* (let ((production (instantiation-production *instantiation*))) (cond ((and *abort-instantiation* *enable-rational-analysis* (floatp cycles) (> (+ *time* matching-latency) cycles)) (signal-output *latency-trace* "Time ~6,3F: ~A Aborted" *time* production)) (t (when (or *base-level-learning* *associative-learning* *strength-learning*) (learn-matching)) (when *enable-rational-analysis* (incf *time* matching-latency) (when (plusp matching-latency) (signal-output *latency-trace* "Latency ~6,3F: ~A Matching" matching-latency production))) (signal-output *cycle-trace* "Time ~6,3F: ~A Selected" *time* production) (setf (instantiation-latency *instantiation*) (- matching-latency failed-latencies)) (setf action (production-action-cost production)))))) ((and *enable-rational-analysis* *pop-upon-failure* (null *retrieval-scheduler*)) (incf *time* matching-latency) (when (plusp matching-latency) (signal-output *latency-trace* "Latency ~6,3F: Failure Matching" matching-latency)) (setf action *default-action-time*)) (t (signal-output *latency-trace* "Time ~6,3F: No Instantiation Found." *time*))) action)) (defun action-cycle (action-time) "Implements the production action (rhs) cycle called by run-fct and the PM scheduler. The argument action-time holds the latency of the action cycle" ;; Reset *latency* for the action retrievals (setf *latency* 0.0) (cond (*instantiation* (let* ((production (instantiation-production *instantiation*)) (success (< (random 1.0) (production-action-probability production)))) (when (member (production-name production) *break-productions* :test #'eq) (pprint-instantiation) (break "Production ~S is about to fire.~%" production)) ;; increment time by action latency before production firing (incf *time* action-time) (signal-output *latency-trace* "Latency ~6,3F: ~A Action" action-time production) (cond (success (when *enable-production-learning* (store-instantiation *instantiation*)) (when *firing-hook-fn* (funcall *firing-hook-fn* *instantiation*)) (dolist (rhs-call (production-rhs production)) (simulate-call rhs-call)) (signal-output *cycle-trace* "Time ~6,3F: ~A Fired" *time* production) (when *enable-rational-analysis* (update-activation-spread))) (t (signal-output *cycle-trace* "Time ~6,3F: ~A Fails" *time* production))) (when *parameters-learning* (learn-parameters (get-functional-parameter (production-success production)) (get-functional-parameter (production-failure production)) :latency (+ (instantiation-latency *instantiation*) action-time))))) (t (when *goal-stack* (let ((failure (get-wme 'failure))) (unless failure ; if failure isn't yet defined then add it (add-dm-fct '((failure isa error condition failure)) :reset-ia nil) (setf failure (get-wme 'failure))) (dolist (return (goal-frame-return-values (first *goal-stack*))) (set-slot-value *wmfocus* (first return) failure)))) (incf *time* action-time) (signal-output *latency-trace* "Latency ~6,3F: Failure Action" action-time) (signal-output *cycle-trace* "Time ~6,3F: ~A Popped in Failure" *wmfocus* *time*) (pop-fct "failure") (when *parameters-learning* (learn-parameters nil t :instantiation nil))))) (defun retrieval-event (&optional (time *time*)) "Sets the retrieval if the current time is past the retrieval time. If the time passed as argument is after the current time, this implements some lookahead capability." (when (and *retrieval-scheduler* (>= time (car *retrieval-scheduler*))) (signal-output *cycle-trace* "Time ~6,3F: ~A Retrieved" (car *retrieval-scheduler*) (cdr *retrieval-scheduler*)) (setf *retrieval* (cdr *retrieval-scheduler*)) (setf *retrieval-scheduler* nil)) ) (defun run-fct (&optional (cycles -1)) (let ((start-time *time*) (start-cycle *cycle*) (retrieval-threshold *retrieval-threshold*) (utility-threshold *utility-threshold*)) (cond ((integerp cycles) (incf cycles *cycle*)) ((floatp cycles) (incf cycles *time*)) (t (signal-warn "ARGUMENT TO RUN COMMAND MUST BE A NUMBER."))) (loop ;;; sets the time of the start of the production cycle (setf *start-time* *time*) (retrieval-event) (cond (*stop* (signal-output *latency-trace* "Time ~6,3F: Stop Requested" *time*) (setf *stop* nil) (return)) ; ((null *wmfocus*) ; (signal-output *latency-trace* "Time ~6,3F: No Goal" *time*) ; (return)) ((and (integerp cycles) (= *cycle* cycles)) (signal-output *latency-trace* "Time ~6,3F: Stopped by Run Cycle Limit" *time*) (return)) ((and (floatp cycles) (>= *time* cycles)) (signal-output *latency-trace* "Time ~6,3F: Stopped by Run Time Limit" *time*) (return)) (t)) (save-state-change :run *time* *cycle* *spread-stamp* (make-random-state)) ;;; production cycle (let ((action-time (matching-cycle cycles retrieval-threshold utility-threshold))) (cond (action-time ;;; if production left-hand side match (retrieval-event (+ *time* action-time)) ;;; for retrievals during action (action-cycle action-time)) ;;; then execute the right-hand side action (*retrieval-scheduler* ;;; if retrieval event pending (setf *time* (if (floatp cycles) ;; schedule if before end of run quantum (min cycles (car *retrieval-scheduler*)) (car *retrieval-scheduler*)))) (t (return)))) ;;; else quit (incf *cycle* 1) (when *cycle-hook-fn* (funcall *cycle-hook-fn* *instantiation*)) (when *web-hook-fn* (funcall *web-hook-fn* *instantiation*)) ) ;; Reset at the end because of aborted cycles (setf *retrieval-threshold* retrieval-threshold) (setf *utility-threshold* utility-threshold) (when *end-run-hook-fn* (funcall *end-run-hook-fn* (- *time* start-time))) (signal-output *latency-trace* "Run Latency: ~6,3F" (- *time* start-time)) (values (* 0.001 (round (- *time* start-time) 0.001)) (- *cycle* start-cycle)))) (defun step-fct (conflict-set) "Asks the user to choose between step, stop, run, or select." (loop (signal-output *command-trace* "Another step? [Y] step, [N] stop, [R] run, [#] select: ") (let ((input (read))) (cond ((eq input 'y) (return conflict-set)) ((eq input 'n) (setf *stop* t) (return conflict-set)) ((eq input 'r) (setf *conflict-set-hook-fn* nil) (setf *matches-trace* nil) (return conflict-set)) ((and (integerp input) (> input 0) (<= input (length conflict-set))) (return (nth (- input 1) conflict-set))) (t (signal-output *command-trace* "Unknown entry ~S" input)))))) (defun pstep-fct (&optional (cycles -1)) "Runs for a specified number of cycles (if integer) or time (if real) or forever if no value specified. At each cycle, displays the instantiations with matches trace, then allow the user to decide what to do next." (let ((*matches-trace* (or *matches-trace* t)) (*verbose* t) (*conflict-set-hook-fn* *step-fn*)) (run-fct cycles))) (defun run-many-fct (&optional (n 1)) "Runs the whole wmfocus list n times." (dotimes (i n) (dolist (goal *wmfocus-list*) (set-wmfocus :wme goal :spread t) (run-fct)))) ;;; Setting of global parameters (defmacro assign-stream (parameter value) "Handles the (de)assigning of streams to traces." `(progn (when (streamp ,parameter) (close ,parameter)) (when (or (stringp ,value) (pathnamep ,value)) (setf ,value (open ,value :direction :output :if-exists :append :if-does-not-exist :create))))) (defmacro sigp (parameter par test warning &rest housekeeping) "Sets individual global parameter. Tests that value (if not no-set) satisfies test before setting parameter and doing housekeeping, otherwise issue warning." `(cond (,test (setf ,parameter value) ,@housekeeping value) (t (signal-warn "GLOBAL PARAMETER ~A CANNOT TAKE VALUE ~S BECAUSE IT MUST BE ~A." ,par value ,warning) :error))) (defmacro variance-to-s (variance) "Given a variance, returns the corresponding value." `(when ,variance (/ (sqrt (* 3.0 ,variance)) 3.1416))) (defmacro s-to-variance (s) "Given s, returns the corresponding variance." `(when ,s (let ((pis (* 3.1416 ,s))) (/ (* pis pis) 3.0)))) (defun global-parameters-fct (&optional parameters) "Return the value of the global parameters, or print them all if none specified." (let* ((verbose *verbose*) (*verbose* t) (*goal-depth* 1) (value nil) (values nil)) (cond (parameters (dolist (parameter parameters values) (setf value (case parameter ((:esc :era) *enable-rational-analysis*) (:g *g*) (:egn (s-to-variance *exp-gain-noise*)) (:egs *exp-gain-noise*) (:er *enable-randomness*) (:ut *utility-threshold*) (:ga *goal-activation*) (:blc *base-level-constant*) (:an (s-to-variance *activation-noise*)) (:ans *activation-noise*) (:pan (s-to-variance *permanent-activation-noise*)) (:pas *permanent-activation-noise*) (:lf *latency-factor*) (:le *latency-exponent*) (:dat *default-action-time*) (:pm *partial-matching*) (:mp *mismatch-penalty*) (:ms *max-sim*) (:md *max-dif*) (:rt *retrieval-threshold*) (:tmp *temperature*) (:bln *blending*) (:cp *cost-penalty*) (:ie *initial-experience*) (:tt *threshold-time*) (:epl *enable-production-learning*) (:ol *optimized-learning*) (:bll *base-level-learning*) (:al *associative-learning*) (:sl *strength-learning*) (:pl *parameters-learning*) (:emt *exact-matching-trace*) (:pmt *partial-matching-trace*) (:pct *production-compilation-trace*) (:act *activation-trace*) (:blt *blending-trace*) (:crt *conflict-resolution-trace*) (:cst *conflict-set-trace*) (:mt *matches-trace*) (:pt *production-trace*) (:ct *cycle-trace*) (:lt *latency-trace*) (:ot *output-trace*) (:dmt *dm-trace*) (:gt *goal-trace*) (:v verbose) (t (signal-warn "NO GLOBAL PARAMETER ~A DEFINED" parameter) :error))) (push-last value values) (signal-output *command-trace* "~S ~6,3F" parameter value))) (t (setf values (list *enable-rational-analysis* *g* *exp-gain-noise* *enable-randomness* *utility-threshold* *goal-activation* *base-level-constant* *activation-noise* *permanent-activation-noise* *latency-factor* *latency-exponent* *default-action-time* *partial-matching* *mismatch-penalty* *max-sim* *max-dif* *retrieval-threshold* *temperature* *blending* *optimized-learning* *base-level-learning* *associative-learning* *strength-learning* *parameters-learning* *cost-penalty* *initial-experience* *threshold-time* *enable-production-learning* *exact-matching-trace* *partial-matching-trace* *production-compilation-trace* *activation-trace* *blending-trace* *conflict-resolution-trace* *conflict-set-trace* *matches-trace* *production-trace* *cycle-trace* *latency-trace* *output-trace* *dm-trace* *goal-trace* verbose)) (signal-output *command-trace* "~?" "Enable Subsymbolic Computations (esc): ~S~% G (g): ~S~%~ ~1TExpected Gain S (egs): ~S~% Enable Randomness (er): ~S~%~ ~1TUtility Threshold (ut): ~S~%~ ~1T--------------------~%~ ~1TGoal Activation (ga): ~S~% Base Level Constant (blc): ~S~%~ ~1TActivation Noise S (ans): ~S~% Permanent Activation S (pas): ~S~%~ ~1T--------------------~%~ ~1TLatency Factor (lf): ~S~%~ ~1TLatency Exponent (le): ~S~% Default Action Time (dat): ~S~%~ ~1T--------------------~%~ ~1TPartial Matching (pm): ~S~% Mismatch Penalty (mp): ~S~%~ ~1TMaximum Similarity (ms): ~S~% Maximum Difference (md): ~S~%~ ~1TRetrieval Threshold (rt): ~S~%~ ~1TTemperature (tmp): ~S~% Blending (bln): ~S~%~ ~1T--------------------~%~ ~1TOptimized Learning (ol): ~S~% Base Level Learning (bll): ~S~%~ ~1TAssociative Learning (al): ~S~% Strength Learning (sl): ~S~%~ ~1TParameters Learning (pl): ~S~% Cost Penalty (cp): ~S~%~ ~1TInitial Experience (ie): ~S~% Threshold Time (tt): ~S~% Enable Production Learning (epl): ~S~%~ ~1T--------------------~%~ ~1TExact Matching Trace (emt): ~S~% Partial Matching Trace (pmt): ~S~%~ ~1TProduction Compilation Trace (pct): ~S~%~ ~1TActivation Trace (act): ~S~% Blending Trace (blt): ~S~%~ ~1TConflict Resolution Trace (crt): ~S~% Conflict Set Trace (cst): ~S~%~ ~1TMatches Trace (mt): ~S~% Production Trace (pt): ~S~% Cycle Trace (ct): ~S~%~ ~1TLatency Trace (lt): ~S~% Output Trace (ot): ~S~%~ ~1TDeclarative Memory Trace (dmt): ~S~% Goal Trace (gt): ~S~% Verbose (v): ~S~%" values) nil)))) (defun set-global-parameters-fct (parameters) "Sets global parameters." (let ((values nil) (parameter nil) (value nil)) (loop (unless parameters (return values)) (setf parameter (pop parameters)) (setf value (pop parameters)) (when (and (listp value) (eq (first value) 'quote)) (setf value (second value))) ;; for compatibility with evaluating versions (push-last (case parameter (:dat (sigp *default-action-time* :dat (and (numberp value) (>= value 0.0)) "a non-negative number")) (:ga (sigp *goal-activation* :ga (and (numberp value) (>= value 0.0)) "a non-negative number" (update-activation-spread))) (:g (sigp *g* :g (and (numberp value) (>= value 0.0)) "a non-negative number" (all-pg-c))) (:egn (sigp *exp-gain-noise* :egn (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (setf *exp-gain-noise* (variance-to-s *exp-gain-noise*)))) (:egs (sigp *exp-gain-noise* :egs (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number")) (:ut (sigp *utility-threshold* :ut (or (null value) (numberp value)) "NIL or a number")) (:lf (sigp *latency-factor* :lf (and (numberp value) (>= value 0.0)) "a non-negative number")) (:le (sigp *latency-exponent* :le (and (numberp value) (>= value 0.0)) "a non-negative number")) (:blc (let ((old-blc *base-level-constant*)) (sigp *base-level-constant* :blc (numberp value) "a number" (let ((base-level-inc (- value old-blc))) (for-all-wmes wme (incf (wme-base-level wme) base-level-inc) (incf (wme-activation wme) base-level-inc)))))) (:an (sigp *activation-noise* :an (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (progn (setf *activation-noise* (variance-to-s *activation-noise*)) (for-all-wmes wme (decf (wme-time-stamp wme) 1.0))))) (:ans (sigp *activation-noise* :as (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (for-all-wmes wme (decf (wme-time-stamp wme) 1.0)))) (:pan (sigp *permanent-activation-noise* :pan (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (progn (setf *permanent-activation-noise* (variance-to-s *permanent-activation-noise*)) (for-all-wmes wme (let ((noise (if value (noise value) 0.0))) (incf (wme-activation wme) (- noise (wme-permanent-noise wme))) (setf (wme-permanent-noise wme) noise)))))) (:pas (sigp *permanent-activation-noise* :pas (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (for-all-wmes wme (let ((noise (if value (noise value) 0.0))) (incf (wme-activation wme) (- noise (wme-permanent-noise wme))) (setf (wme-permanent-noise wme) noise))))) (:mp (sigp *mismatch-penalty* :mp (and (numberp value) (>= value 0.0)) "a non-negative number")) (:ms (sigp *max-sim* :ms (numberp value) "a number" (for-all-wmes wme (set-similarity wme wme *max-sim*)))) (:md (sigp *max-dif* :md (numberp value) "a number")) (:tmp (sigp *temperature* :tmp (or (null value) (and (numberp value) (> value 0.0))) "NIL or a positive number")) (:bln (sigp *blending* :bln (or (null value) (eq value t) (eq value 'rt)) "NIL, T or RT")) ; FIX: switch representation when :era is activated. ; Make sure to not re-apply for a noop. ((:esc :era) (unless (eq *enable-rational-analysis* value) ;;; No guarantee of goal or ordering but use of special function for buffers: ;;; use nsublis for all of the lhs AND the rhs!! (sigp *enable-rational-analysis* :era (or (null value) (eq value t)) "T or NIL" (if *enable-rational-analysis* (let ((substs '((direct-test-and-bind . direct-test-and-bind-pm) (indirect-test-and-bind . indirect-test-and-bind-pm)))) (dolist (production *procedural-memory*) (let* ((lhs (production-lhs (cdr production))) (direct-length (first-retrieval-index lhs (production-initializations (cdr production))))) (setf (production-lhs (cdr production)) (cons (nconc (subseq lhs 0 direct-length) (last lhs)) (nconc (butlast (subseq lhs direct-length)) (list 'not)))) (setf (production-lhs (cdr production)) (nsublis substs (production-lhs (cdr production)) :test #'eq)) (setf (production-rhs (cdr production)) (nsublis substs (production-rhs (cdr production)) :test #'eq)) )) (update-activation-spread) (unless *retrieval-threshold* (setf *retrieval-threshold* 0.0))) (let ((substs '((direct-test-and-bind-pm . direct-test-and-bind) (indirect-test-and-bind-pm . indirect-test-and-bind)))) (dolist (production *procedural-memory*) (let ((lhs (production-lhs (cdr production)))) (setf (production-lhs (cdr production)) (nconc (butlast (first lhs)) (butlast (rest lhs)) (last (first lhs))))) (setf (production-lhs (cdr production)) (nsublis substs (production-lhs (cdr production)) :test #'eq)) (setf (production-rhs (cdr production)) (nsublis substs (production-rhs (cdr production)) :test #'eq)) ) (setf *retrieval-threshold* nil)))))) ; FIX: call sgp-fct recursively instead of setting :era directly (:rt (sigp *retrieval-threshold* :rt (or (null value) (numberp value)) "NIL or a number" (if *retrieval-threshold* (unless *enable-rational-analysis* (sgp-fct '(:era t))) (when *enable-rational-analysis* (sgp-fct '(:era nil)))))) ; FIX: now switch representation on :era rather than :pm (:pm (sigp *partial-matching* :pm (or (null value) (eq value t)) "T or NIL")) (:er (sigp *enable-randomness* :er (or (null value) (eq value t)) "T or NIL")) (:ol (sigp *optimized-learning* :ol (or (null value) (eq value t) (and (integerp value) (plusp value))) "T, NIL or a positive integer" (for-all-wmes wme (adapt-references (wme-references wme) (wme-creation-time wme)) (decf (wme-time-stamp wme) 1.0)) (dolist (production *procedural-memory*) (adapt-references (production-references (cdr production)) (production-creation-time (cdr production))) (decf (production-time-stamp (cdr production)) 1.0)))) (:bll (let ((old-bll *base-level-learning*)) (sigp *base-level-learning* :bll (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (if (and old-bll (null value)) ;;; when turning bll off, update all base levels (let ((*base-level-learning* old-bll)) (for-all-wmes wme (compute-base-level-activation wme))) (for-all-wmes wme (decf (wme-time-stamp wme) 1.0)))))) (:al (let ((old-al *associative-learning*)) (sigp *associative-learning* :al (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (when (and old-al (null value)) ;;; when turning al off, update all IAs (let ((*associative-learning* old-al)) (for-all-wmes wme (dolist (wme-ia (wme-ias wme)) (ia-value (cdr wme-ia) (car wme-ia) wme))))) (incf *spread-stamp* 1)))) (:sl (let ((old-sl *strength-learning*)) (sigp *strength-learning* :sl (or (null value) (and (numberp value) (>= value 0.0))) "either NIL or a non-negative number" (when (and old-sl (null value)) ;;; when turning sl off, update all production strengths (let ((*strength-learning* old-sl)) (dolist (production *procedural-memory*) (strength (cdr production))))) (dolist (production *procedural-memory*) (decf (production-time-stamp (cdr production)) 1.0))))) (:pl (sigp *parameters-learning* :pl (or (null value) (eq value t) (numberp value)) "T, NIL or a NUMBER" (when *parameters-learning* (dolist (production *procedural-memory*) (setf production (cdr production)) (adapt-references (production-successes production) (production-creation-time production) (not (numberp *parameters-learning*))) (adapt-references (production-failures production) (production-creation-time production) (not (numberp *parameters-learning*))) (when (numberp *parameters-learning*) (let ((successes (round (first (production-successes production)))) (efforts (first (production-efforts production)))) (setf (production-efforts production) (cons efforts (make-list successes :initial-element (/ efforts successes)))))) (recompute-production-parameters production))))) (:cp (sigp *cost-penalty* :cp (numberp value) "a number")) (:ie (sigp *initial-experience* :ie (and (numberp value) (>= value 0.0)) "a non-negative number")) (:tt (sigp *threshold-time* :tt (and (numberp value) (>= value 0.0)) "a non-negative number")) (:epl (sigp *enable-production-learning* :epl (or (null value) (eq value t)) "T or NIL")) (:ot (assign-stream *output-trace* value) (sigp *output-trace* :ot (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:ct (assign-stream *cycle-trace* value) (sigp *cycle-trace* :ct (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:lt (assign-stream *latency-trace* value) (sigp *latency-trace* :lt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:pmt (assign-stream *partial-matching-trace* value) (sigp *partial-matching-trace* :pmt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:pct (assign-stream *production-compilation-trace* value) (sigp *production-compilation-trace* :at (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:act (assign-stream *activation-trace* value) (sigp *activation-trace* :act (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME" (when *activation-trace* (setf *enable-rational-analysis* t)))) (:blt (assign-stream *blending-trace* value) (sigp *blending-trace* :blt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:crt (assign-stream *conflict-resolution-trace* value) (sigp *conflict-resolution-trace* :crt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:cst (assign-stream *conflict-set-trace* value) (sigp *conflict-set-trace* :cst (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:gt (assign-stream *goal-trace* value) (sigp *goal-trace* :gt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:dmt (assign-stream *dm-trace* value) (sigp *dm-trace* :dmt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:pt (assign-stream *production-trace* value) (sigp *production-trace* :pt (or (null value) (eq value t) (eq value 'short) (streamp value)) "T, SHORT, NIL or a valid PATHNAME")) (:mt (assign-stream *matches-trace* value) (sigp *matches-trace* :mt (or (null value) (eq value t) (eq value 'short) (streamp value)) "T, SHORT, NIL or a valid PATHNAME")) (:emt (assign-stream *exact-matching-trace* value) (sigp *exact-matching-trace* :emt (or (null value) (eq value t) (streamp value)) "T, NIL or a valid PATHNAME")) (:v (sigp *verbose* :v (or (null value) (eq value t)) "T or NIL")) (t (signal-warn "NO GLOBAL PARAMETER ~A DEFINED" parameter) :error)) values)))) (defun sgp-fct (&optional parameters) "Inspects and sets global parameters." (if (and (rest parameters) (not (keywordp (second parameters)))) (set-global-parameters-fct parameters) (global-parameters-fct parameters))) (defun pset-fct (sets) "Prints, defines and activates sets of parameters." (let ((*verbose* t) (*goal-depth* 1)) (if (null sets) ;; if no argument is given, then displays the current sets (dolist (set *parameter-sets*) (signal-output *command-trace* "Parameter set ~S:" (first set)) (dolist (parameter (rest set)) (signal-output *command-trace* "~S" parameter)) (signal-output *command-trace* "")) (if (listp sets) ; standard format through pset command (if (and (atom (first sets)) (null (rest sets))) ;; if a single name is given, then activate that set (let ((set (assoc (first sets) *parameter-sets* :test #'equal))) (if set (dolist (parameter (rest set)) (cond ((fboundp (first parameter)) (eval parameter)) ((get-production (first parameter)) ;; interpreted as parameters command by default (parameters-fct (first parameter) (rest parameter))) (t (signal-warn "UNKNOWN PARAMETER COMMAND ~S IN SET ~S" parameter (first sets))))) (signal-warn "UNKNOWN PARAMETER SET ~S" (first sets)))) ; if a list or lists are given, then create the new set(s) (dolist (set (if (atom (first sets)) (list sets) sets)) (let ((old-set (assoc (first set) *parameter-sets* :test #'equal))) (cond (old-set (signal-warn "REDEFINING PARAMETER SET ~S" (first set)) (rplacd old-set (rest set))) (t (push-last set *parameter-sets*)))))) (signal-warn "UNKNOWN ARGUMENT FORMAT ~S FOR PSET COMMAND" sets))))) (defun set-g-fct (g &key threshold) "Sets G and perhaps its threshold. Provided for compatibility only. Use sgp." (sgp-fct (list :g g)) (when threshold (sgp-fct (list :gth threshold)))) (defun output-stream-fct (file &key echo) "Switches output trace to file." (if (or (stringp file) (pathnamep file)) (let ((stream (open file :direction :output :if-exists :append :if-does-not-exist :create))) (when echo (setf stream (make-broadcast-stream stream *standard-output*))) (sgp-fct (list :ot stream)) stream) (signal-warn "ARGUMENT ~S TO OUTPUT-STREAM SHOULD BE A VALID FILENAME." file))) (defun close-output-fct () "Closes the output trace stream." (sgp-fct (list :ot t))) (defun trace-stream-fct (file &key echo) "Switches all trace outputs to file." (if (or (stringp file) (pathnamep file)) (let ((stream (open file :direction :output :if-exists :append :if-does-not-exist :create))) (when echo (setf stream (make-broadcast-stream stream *standard-output*))) (sgp-fct (list :ct (and *cycle-trace* stream) :lt (and *latency-trace* stream) :pmt (and *partial-matching-trace* stream) :pct (and *production-compilation-trace* stream) :act (and *activation-trace* stream) :blt (and *blending-trace* stream) :crt (and *conflict-resolution-trace* stream) :cst (and *conflict-set-trace* stream) :gt (and *goal-trace* stream) :dmt (and *dm-trace* stream) :pt (and *production-trace* stream) :mt (and *matches-trace* stream) :emt (and *exact-matching-trace* stream))) stream) (signal-warn "ARGUMENT ~S TO TRACE-STREAM SHOULD BE A VALID FILENAME." file))) (defun close-trace-fct () "Closes the trace stream." (let ((stream t)) (sgp-fct (list :ct (and *cycle-trace* stream) :lt (and *latency-trace* stream) :pmt (and *partial-matching-trace* stream) :pct (and *production-compilation-trace* stream) :act (and *activation-trace* stream) :blt (and *blending-trace* stream) :crt (and *conflict-resolution-trace* stream) :cst (and *conflict-set-trace* stream) :gt (and *goal-trace* stream) :dmt (and *dm-trace* stream) :pt (and *production-trace* stream) :mt (and *matches-trace* stream) :emt (and *exact-matching-trace* stream))))) (defun construct-pathname (file directory) "Constructs a complete pathname from file and directory." (make-pathname :host (pathname-host directory) :device (pathname-device directory) :directory (pathname-directory directory) :name (pathname-name file) :type (pathname-type file))) (defun load-model-list (file &optional (save nil)) (with-open-file (model file) (loop (let ((expr (read model nil :end))) (when (eq expr :end) (return)) (when (and (not save) (listp expr) (member (first expr) '(clearall clearall-fct clear-all clear-all-fct))) (setf save t)) (when save (cond ((eq (first expr) 'load) (load-model-list (second expr) t)) ((eq (first expr) 'load-model) (load-model-list (construct-pathname (second expr) (or (third expr) file)) t)) (t (push-last expr *model*)))))))) (defun seed (&optional (seed t)) "Randomized the random number generator by running it seed times." (when (eq seed t) (setf seed (decode-universal-time (get-universal-time)))) (cond ((integerp seed) (dotimes (i seed) (random 1.0))) ((random-state-p seed) (setf *random-state* (make-random-state seed))) (seed (signal-warn "UNKNOWN ARGUMENT ~S TO SEED FUNCTION." seed)) (t))) (seed) (defun reset-fct (&optional (seed t)) "Resets the current model by reevaluating all the commands." (cond ((rest *model*) (seed seed) (dolist (command (rest *model*)) (eval command))) (t (reload-fct seed)))) (defun load-model-fct (file &optional (directory *load-pathname*)) "Loads model file at directory, which defaults to the local folder. Sets *load-pathname* for those Lisps where it is not done." (let ((*load-pathname* (construct-pathname file directory))) (when (load *load-pathname* :verbose *verbose*) *load-pathname*))) (defun reload-fct (&optional (seed t)) "Updates the current model by reloading the file." (cond ((first *model*) (seed seed) (load (first *model*) :verbose *verbose*)) (t (signal-warn "NO MODEL STORED. LOAD THE MODEL FILE BEFORE USING RESET OR RELOAD.~%~ DO NOT LOAD MODEL BY EVALUATING THE BUFFER.")))) (defun import-model-fct (file) "Compresses a bunch of environment files into file, which has to be a string of the form : where is where the files are located and is their name minus the extensions (Chunk Types, Chunks, Productions)." (let ((directory (directory-namestring file)) (name (file-namestring file)) (lines nil)) (with-open-file (common file :direction :output :if-exists :supersede :if-does-not-exist :create) ; first the code (with-open-file (misc (make-pathname :directory directory :name (concatenate 'string name " Misc")) :direction :input :if-does-not-exist nil) (when misc (let ((code nil)) (loop (let ((line (read-line misc nil :end))) (cond ((eq line :end) (return)) ((equal line "") (setf code nil)) ((equal (subseq line 0 4) "(def") (setf code t))) (if code (format common "~A~%" line) (push-last line lines))))))) (format common "~%(clear-all)~2%") (clear-all-fct) (with-open-file (global (make-pathname :directory directory :name "Global-Parameters") :direction :input :if-does-not-exist nil) (when global (let ((keyword-values nil)) (loop (let ((exp (read global nil :end))) (cond ((eq exp :end) (return)) ((and (listp exp) (eq (first exp) 'sgp)) (pop exp) (loop (unless exp (return)) (let ((keyword (pop exp)) (value (pop exp))) (unless (equal value (let ((*command-trace* nil)) (first (sgp-fct (list keyword))))) (push value keyword-values) (push keyword keyword-values))))) (t (signal-warn "UNKNOWN COMMAND ~S IN GLOBAL-PARAMETERS FILE" exp))))) (when keyword-values (format common "(sgp~{ ~(~S~)~})~2%" keyword-values))))) (dolist (extension '("Chunk Types" "Chunks" "Productions")) (with-open-file (part (make-pathname :directory directory :name (concatenate 'string name " " extension)) :direction :input :if-does-not-exist nil) (when part (loop (let ((line (read-line part nil :end))) (if (eq line :end) (return) (format common "~A~%" line)))) (terpri common)))) (dolist (line lines) (format common "~A~%" line))))) (clear-all-fct nil) (defun pundo-fct (&optional arg) "Undoes production firings. If arg is nil, toggles the Save State Change mode. If arg is a number, undoes that number of production firings. If arg is t, undo all production firings saved." (cond ((null arg) (setf *save-state-changes* (if *save-state-changes* nil (list :on)))) ((or (integerp arg) (eq arg t)) (let* ((cycles (if (eq arg t) -1 arg)) (command nil) (keyword nil) (state-changes *save-state-changes*)) (setf *save-state-changes* nil) (if state-changes (loop (when (or (zerop cycles) (eq (first state-changes) :on)) (return arg)) (decf cycles 1.0) (loop (setf command (pop state-changes)) (setf keyword (pop command)) (case keyword (:create-wme (delete-wme (first command))) (:set-slot-value (set-slot-value (first command) (second command) (third command))) (:delete-wme (let ((wme (first command))) (incf *wme-number* 1) (setf (get-wme (wme-name wme)) wme) (push-last wme (wme-type-wmes (wme-type wme))))) (:delete-production (let ((production (first command))) (push-last (cons (production-name production) production) *procedural-memory*) (push-last production (wme-type-productions (production-goal-type production))))) (:add-reference (let ((references (first command))) (decf (first references) 1.0) (pop (rest references)))) (:compile-production (delete-production (first command))) (:penable-fct (pdisable-fct (first command))) (:pdisable-fct (penable-fct (first command))) (:focus-on (let ((frame (first command))) (focus-macro (goal-frame-focus frame) (goal-frame-return-values frame)))) (:pop-goal (let ((frame (first command))) (push-macro (goal-frame-focus frame) (goal-frame-return-values frame)))) (:push-goal (pop-macro)) (:run (when *enable-rational-analysis* (update-activation-spread)) (setf *time* (first command)) (setf *cycle* (second command)) (setf *spread-stamp* (third command)) (setf *random-state* (make-random-state (fourth command))) (return)) (otherwise (signal-error "UNKNOWN PUNDO OPERATION ~S" keyword))))) (signal-warn "UNDOING MUST FIRST BE TURNED ON WITH (PUNDO).")) (setf *save-state-changes* state-changes)) (for-all-wmes wme (setf (wme-time-stamp wme) (- *time* 1.0)) (setf (wme-spread-stamp wme) (- *spread-stamp* 1))) (dolist (production *procedural-memory*) (setf (production-time-stamp (cdr production)) (- *time* 1.0))) *cycle*) (t (signal-warn "UNKNOWN ARGUMENT TO PUNDO: ~S" arg)))) (defun help-fct (&optional (commands nil)) "Outputs a short description of a list of ACT-R commands, or the full list of commands if none is supplied." (let ((*verbose* t) (*goal-depth* 1)) (if commands (dolist (command commands) (signal-output *command-trace* "~A" command) (signal-output *command-trace* "~A" (documentation command 'function))) (dolist (command-pair *command-mappings*) (signal-output *command-trace* "~A" (first command-pair)))))) ;;; ;;; USER-LEVEL MACROS ;;; (defmacro clear-all (&optional (save-model t)) "Clears the whole system." `(clear-all-fct ',save-model)) (defmacro clear-dm () "Clears working memory elements." `(clear-dm-fct)) (defmacro clear-productions () "Clears all productions." `(clear-productions-fct)) (defmacro actr-time (&optional inc) "Returns the current act-r time, or adds inc to it if specified." `(actr-time-fct ',inc)) (defmacro chunk-type (&rest arguments) "The user-level command to define a new wme type." `(chunk-type-fct ',arguments)) (defmacro new-name (&optional (name "CHUNK")) "Returns a unique symbol as generated by gentemp based on name, which can be either a string, a symbol, or else defaults to CHUNK." `(new-name-fct ',name)) (defmacro add-dm (&rest wmes) "Adds the following wmes to working memory." `(add-dm-fct ',wmes)) (defmacro set-dm (&rest wmes) "Adds the following wmes to working memory." `(set-dm-fct ',wmes)) (defmacro copy-chunk (&rest wmes) "Makes copies of the wmes." `(copy-chunk-fct ',wmes)) (defmacro delete-chunk (&rest wmes) "Deletes the following wmes from working memory." `(delete-chunk-fct ',wmes)) (defmacro get-base-level (&rest wmes) "Returns the base level of wme." `(get-base-level-fct ',wmes)) (defmacro set-all-base-levels (references &optional (creation-time nil)) "Sets all base level activations." `(set-all-base-levels-fct ',references ',creation-time)) (defmacro set-base-levels (&rest settings) "Sets base level activations." `(set-base-levels-fct ',settings)) (defmacro set-general-base-levels (&rest settings) "Sets base level activations. Same as set-base-levels." `(set-general-base-levels-fct ',settings)) (defmacro add-ia (&rest settings) "Sets individual ias." `(add-ia-fct ',settings)) (defmacro set-ia (&rest settings) "Sets individual ias." `(set-ia-fct ',settings)) (defmacro activation-sources () "Displays activation sources." `(activation-sources-fct)) (defmacro update-activation () "Updates the activation of all wmes by recomputing it." `(update-activation-fct)) (defmacro chunk-slot-value (wme slot) "Returns the slot value of wme." `(chunk-slot-value-fct ',wme ',slot)) (defmacro mod-chunk (wme &rest slot-values) "Sets slots of wme to values." `(mod-chunk-fct ',wme ',slot-values)) (defmacro mod-focus (&rest slot-values) "Sets slots of wm focus to values." `(mod-focus-fct ',slot-values)) (defmacro get-name (&rest wmes-or-productions) "Returns the names of a list of wmes or productions." `(get-name-fct ',wmes-or-productions)) (defmacro dm (&rest wmes) "Prints the following wmes. If none specified, print them all." `(dm-fct ',wmes)) (defmacro sdm (&rest slot-values) "Prints wmes which have values in slots." `(sdm-fct ',slot-values)) (defmacro pmatches () "Generates and prints the instantiations which match the current wm state." `(pmatches-fct)) (defmacro whynot (&rest productions) "Tries to match production(s) with exact matching trace on." `(whynot-fct ',productions)) (defmacro whynot-dependency (&rest wmes) "Tries analogy with wme(s) as examples." `(whynot-dependency-fct ',wmes)) (defmacro reset-ia () "Resets all the ia values, preserving user-specified and learned values." `(reset-ia-fct)) (defmacro ia (wmej wmei) "Returns the ia value between wmej and wmei, in that order." `(ia-fct ',wmej ',wmei)) (defmacro similarity (wmej wmei) "Returns the similarity between wmej and wmei, in that order." `(similarity-fct ',wmej ',wmei)) (defmacro set-similarities (&rest triples) "Sets the similarities, i.e a list of triplets containg wmej wmei and value." `(set-similarities-fct ',triples)) (defmacro sdp (&rest wme-parameters) "Inspects and sets wme parameters." `(sdp-fct ',wme-parameters)) (defmacro p (&rest definition) "Production definition." `(p-fct ',definition)) (defmacro penable (&rest productions) "Enables disabled productions." `(penable-fct ',productions)) (defmacro pdisable (&rest productions) "Disables productions." `(pdisable-fct ',productions)) (defmacro pbreak (&rest productions) "Sets break points for productions." `(pbreak-fct ',productions)) (defmacro punbreak (&rest productions) "Removes break points for productions." `(punbreak-fct ',productions)) (defmacro pp (&rest productions) "Prints the following productions. If none specified, print all active ones." `(pp-fct ',productions)) (defmacro production-parameter (production &rest parameters) "Returns value of production parameters, or print them all if none is specified." `(production-parameter-fct ',production ',parameters)) (defmacro parameters (production &rest parameters) "Sets a production parameters using keyword arguments." `(parameters-fct ',production ',parameters)) (defmacro set-compilation-parameters (&rest params) "Sets the parameters for analogized productions" `(set-compilation-parameters-fct ',params)) (defmacro spp (&rest production-parameters) "Inspects and sets production parameters." `(spp-fct ',production-parameters)) (defmacro pset (&rest sets) "Prints, defines and activates sets of parameters." `(pset-fct ',sets)) (defmacro set-g (g &key threshold) "Sets G and perhaps its threshold. Provided for compatibility only. Use sgp." `(set-g-fct ',g :threshold ',threshold)) (defmacro output-stream (file &key echo) "Switches output trace to file." `(output-stream-fct ',file :echo ',echo)) (defmacro close-output () "Closes the output trace stream." `(close-output-fct)) (defmacro trace-stream (file &key echo) "Switches all trace outputs to file." `(trace-stream-fct ',file :echo ',echo)) (defmacro close-trace () "Closes the trace stream." `(close-trace-fct)) (defmacro rehearse-chunk (&rest chunks) "Rehearses chunks. If a chunk is a list, then the first element of the list is the chunk to rehearse and the rest is the list of sources." `(rehearse-chunk-fct ',chunks)) (defmacro buffers (&rest buffers) "Displays and sets the contents of buffers." `(buffers-fct ',buffers)) (defmacro retrieval (&optional (chunk nil)) "Displays or changes (if argument is supplied) the value of the last chunk retrieved." `(retrieval-fct ',chunk)) (defmacro goal-focus (&rest wmes) "Sets the focus to wme or wmes, or prints the current one if none specified." `(goal-focus-fct ',wmes)) (defmacro goal-stack () "Prints the current list of goals on the goal stack." `(goal-stack-fct)) (defmacro push-goal (wme) "Pushes wme on top of the stack." `(push-goal-fct ',wme)) (defmacro pop-goal () "Pops the top goal." `(pop-goal-fct)) (defmacro focus-on-goal (wme) "Pops the top goal then focuses on wme." `(focus-on-goal-fct ',wme)) (defmacro clear-goal-stack () "Clears the goal stack by restoring the top goal." `(clear-goal-stack-fct)) (defmacro run (&optional (cycles -1)) "Runs for a specified number of cycles (if integer) or time (if real) or forever if no value specified." `(run-fct ',cycles)) (defmacro pstep (&optional (cycles -1)) "Runs for a specified number of cycles (if integer) or time (if real) or forever if no value specified. At each cycle, displays the instantiations with matches trace, then allow the user to decide what to do next." `(pstep-fct ',cycles)) (defmacro run-many (&optional (n 1)) "Runs the whole wmfocus list n times." `(run-many-fct ',n)) (defmacro pundo (&optional arg) "Undoes production firings." `(pundo-fct ',arg)) (defmacro reset (&optional (seed t)) "Resets the model by reevaluating it as stored in *model*. Seed can be provided to control reinitialization of random number generator." `(reset-fct ',seed)) (defmacro load-model (file &optional (directory *load-pathname*)) "Loads model file in directory (local by default)." `(load-model-fct ',file ',directory)) (defmacro reload (&optional (seed t)) "Reloads the model by reloading the *model* file. Seed can be provided to control reinitialization of random number generator." `(reload-fct ',seed)) (defmacro import-model (file) "Compresses a bunch of environment files into file, which has to be a string of the form : where is where the files are located and is their name minus the extensions (WMETypes, Productions, etc)." `(import-model-fct ',file)) (defmacro help (&rest commands) "Outputs a short description of one or more ACT-R command(s), or the full list of commands if none is supplied." `(help-fct ',commands)) ;;; ;;; OLD USER-LEVEL MACROS AND FCT FOR COMPATIBILITY PURPOSES ONLY ;;; (defmacro clearall (&optional (save-model t)) "See CLEAR-ALL." `(clear-all-fct ',save-model)) (defun clearall-fct (&optional (save-model t)) "See CLEAR-ALL-FCT." (clear-all-fct save-model)) (defmacro clearwm () "See CLEAR-DM." `(clear-dm-fct)) (defun clearwm-fct () "See CLEAR-DM-FCT." (clear-dm-fct)) (defmacro clearproductions () "See CLEAR-PRODUCTIONS." `(clear-productions-fct)) (defun clearproductions-fct () "See CLEAR-PRODUCTIONS-FCT." (clear-productions-fct)) (defmacro actrtime (&optional inc) "See ACTR-TIME." `(actr-time-fct ',inc)) (defun actrtime-fct (&optional inc) "See ACTR-TIME-FCT." (actr-time-fct inc)) (defmacro wmetype (&rest arguments) "See CHUNK-TYPE." `(chunk-type-fct ',arguments)) (defun wmetype-fct (arguments) "See CHUNK-TYPE-FCT." (chunk-type-fct arguments)) (defmacro addwm (&rest wmes) "See ADD-DM." `(add-dm-fct ',wmes)) (defun addwm-fct (wmes &key (reset-ia t)) "See ADD-DM-FCT." (add-dm-fct wmes :reset-ia reset-ia)) (defmacro setwm (&rest wmes) "See SET-DM." `(set-dm-fct ',wmes)) (defun setwm-fct (wmes) "See SET-DM-FCT." (set-dm-fct wmes)) (defmacro copywme (&rest wmes) "See COPY-CHUNK." `(copy-chunk-fct ',wmes)) (defun copywme-fct (wmes) "See COPY-CHUNK-FCT." (copy-chunk-fct wmes)) (defmacro deletewm (&rest wmes) "See DELETE-CHUNK." `(delete-chunk-fct ',wmes)) (defun deletewm-fct (wmes) "See DELETE-CHUNK-FCT." (delete-chunk-fct wmes)) (defmacro getbaselevel (&rest wmes) "See GET-BASE-LEVEL." `(get-base-level-fct ',wmes)) (defun getbaselevel-fct (wmes) "See GET-BASE-LEVEL-FCT." (get-base-level-fct wmes)) (defmacro setallbaselevels (references &optional (creation-time nil)) "See SET-ALL-BASE-LEVELS." `(set-all-base-levels-fct ',references ',creation-time)) (defun setallbaselevels-fct (references &optional (creation-time nil)) "See SET-ALL-BASE-LEVELS-FCT." (set-all-base-levels-fct references creation-time)) (defmacro setbaselevels (&rest settings) "See SET-BASE-LEVELS." `(set-base-levels-fct ',settings)) (defun setbaselevels-fct (settings) "See SET-BASE-LEVELS-FCT." (set-base-levels-fct settings)) (defmacro setgeneralbaselevels (&rest settings) "See SET-GENERAL-BASE-LEVELS." `(set-general-base-levels-fct ',settings)) (defun setgeneralbaselevels-fct (settings) "See SET-GENERAL-BASE-LEVELS-FCT." (set-general-base-levels-fct settings)) (defmacro addia (&rest settings) "See ADD-IA." `(add-ia-fct ',settings)) (defun addia-fct (settings) "See ADD-IA-FCT." (add-ia-fct settings)) (defmacro setia (&rest settings) "See SET-IA." `(set-ia-fct ',settings)) (defun setia-fct (settings) "See SET-IA-FCT." (set-ia-fct settings)) (defmacro activationsources () "See ACTIVATION-SOURCES." `(activation-sources-fct)) (defun activationsources-fct () "See ACTIVATION-SOURCES-FCT." (activation-sources-fct)) (defmacro wmeslotvalue (wme slot) "See CHUNK-SLOT-VALUE." `(chunk-slot-value-fct ',wme ',slot)) (defun wmeslotvalue-fct (wme slot) "See CHUNK-SLOT-VALUE-FCT." (chunk-slot-value-fct wme slot)) (defmacro modwme (wme &rest slot-values) "See MOD-CHUNK." `(mod-chunk-fct ',wme ',slot-values)) (defun modwme-fct (wme slot-values) "See MOD-CHUNK-FCT." (mod-chunk-fct wme slot-values)) (defmacro modfocus (&rest slot-values) "See MOD-FOCUS." `(mod-focus-fct ',slot-values)) (defun modfocus-fct (slot-values) "See MOD-FOCUS-FCT." (mod-focus-fct slot-values)) (defmacro wm (&rest wmes) "See DM." `(dm-fct ',wmes)) (defun wm-fct (wmes) "See DM-FCT." (dm-fct wmes)) (defmacro swm (&rest slot-values) "See SDM." `(sdm-fct ',slot-values)) (defun swm-fct (slot-values) "See SDM-FCT." (sdm-fct slot-values)) (defmacro resetia () "See RESET-IA." `(reset-ia-fct)) (defun resetia-fct () "See RESET-IA-FCT." (reset-ia-fct)) (defmacro setsimilarities (&rest triples) "See SET-SIMILARITIES." `(set-similarities-fct ',triples)) (defun setsimilarities-fct (triples) "See SET-SIMILARITIES-FCT." (set-similarities-fct triples)) (defmacro swp (&rest wme-parameters) "See SDP." `(sdp-fct ',wme-parameters)) (defun swp-fct (wme-parameters) "See SDP-FCT." (sdp-fct wme-parameters)) (defmacro setanalogizedparameters (&rest params) "See SET-ANALOGIZED-PARAMETERS." `(set-analogized-parameters-fct ',params)) (defmacro setg (g &key threshold) "See SET-G." `(set-g-fct ',g :threshold ',threshold)) (defun setg-fct (g &key threshold) "See SET-G-FCT." (set-g-fct g :threshold threshold)) (defmacro outputstream (file &key echo) "See OUTPUT-STREAM." `(output-stream-fct ',file :echo ',echo)) (defun outputstream-fct (file &key echo) "See OUTPUT-STREAM-FCT." (output-stream-fct file :echo echo)) (defmacro closeoutput () "See CLOSE-OUTPUT." `(closeoutput-fct)) (defun closeoutput-fct () "See CLOSE-OUTPUT-FCT." (close-output-fct)) (defmacro tracestream (file &key echo) "See TRACE-STREAM." `(trace-stream-fct ',file :echo ',echo)) (defun tracestream-fct (file &key echo) "See TRACE-STREAM-FCT." (trace-stream-fct file :echo echo)) (defmacro closetrace () "See CLOSE-TRACE." `(close-trace-fct)) (defun closetrace-fct () "See CLOSE-TRACE-FCT." (close-trace-fct)) (defmacro wmfocus (&rest wmes) "See GOAL-FOCUS." `(goal-focus-fct ',wmes)) (defun wmfocus-fct (&optional (wmes nil)) "See GOAL-FOCUS-FCT." (goal-focus-fct wmes)) (defmacro goalstack () "See GOAL-STACK." `(goal-stack-fct)) (defun goalstack-fct () "See GOAL-STACK-FCT." (goal-stack-fct)) (defmacro push-wme (wme) "See PUSH-GOAL." `(push-goal-fct ',wme)) (defun push-wme-fct (wme) "See PUSH-GOAL-FCT." (push-goal-fct wme)) (defmacro pop-wme () "See POP-GOAL." `(pop-goal-fct)) (defun pop-wme-fct () "See POP-GOAL-FCT." (pop-goal-fct)) (defmacro focus-on-wme (wme) "See FOCUS-ON-GOAL." `(focus-on-goal-fct ',wme)) (defun focus-on-wme-fct (wme) "See FOCUS-ON-GOAL-FCT." (focus-on-goal-fct wme)) (defmacro cleargoalstack () "See CLEAR-GOAL-STACK." `(clear-goal-stack-fct)) (defun cleargoalstack-fct () "See CLEAR-GOAL-STACK-FCT." (clear-goal-stack-fct)) (defmacro whynot-analogy (&rest wmes) "Tries analogy with wme(s) as examples." `(whynot-dependency-fct ',wmes)) (defmacro set-analogized-parameters (&rest params) "Sets the parameters for analogized productions" `(set-compilation-parameters-fct ',params)) ;;; -*- mode: LISP; Package: CL-USER; Syntax: COMMON-LISP; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Author : Mike Byrne ;;; Copyright : (c)2001 Rice U./Mike Byrne, All Rights Reserved ;;; Availability: public domain ;;; Address : Rice University ;;; : Psychology Department ;;; : Houston,TX 77251-1892 ;;; : byrne@acm.org ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Filename : cognition-module.lisp ;;; Version : 2.1b4 ;;; ;;; Description : Class for wrapping around ACT-R to make cognition a ;;; : proper object for the scheduler ;;; ;;; Bugs : ;;; ;;; Todo : [] :AFTER methods to handle buffer stuffing ;;; ;;; ----- History ----- ;;; 01.07.25 Mike Byrne ;;; : Incept date. ;;; 01.08.07 mdb ;;; : Added UPDATE-MODULE and UPDATE-DM-STATE dummy methods. ;;; 01.11.28 mdb/Dan Bothell [b4] ;;; : Changed EXECUTE-RHS and COMPLETE-RETRIEVAL to deal with ;;; : time issues. ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass cognition-module (pm-module) ((ps-state :accessor ps-state :initarg :ps-state :initform :idle) (last-retrieval-request :accessor last-retrieval-request :initarg :last-retrieval-request :initform nil) (min-run-time :accessor min-run-time :initarg :min-run-time :initform 0.050) ) (:default-initargs :name :COGNITION )) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; hadling standard RPM calls (defmethod reset-module :after ((cog-mod cognition-module)) (setf (ps-state cog-mod) :IDLE) (setf (last-retrieval-request cog-mod) nil)) ;;; RUN-MODULE [Method] ;;; Date : 01.07.27 ;;; Description : Has a slightly different RUN-MODULE method here because we ;;; : don't want trace output (this is handled by ACT-R 5) and ;;; : don't need to modify state. (defmethod run-module ((module cognition-module) the-time) (setf (state-change module) nil) (while (and (input-q module) (>= (ms-round the-time) (ms-round (time-tag (first (input-q module)))))) (let* ((input-entry (pop (input-q module))) (the-cmd (first (params input-entry)))) (apply the-cmd (append (list module) (rest (params input-entry))))))) (defmethod update-module ((cog-mod cognition-module)) nil) (defmethod update-dm-state ((cog-mod cognition-module)) nil) (defmethod print-module-state ((cog-mod cognition-module)) (format t "~& ACT-R's state is ~A" (ps-state cog-mod))) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Module messages for interacting with ACT-R (defgeneric select-production (cog-mod &optional max-time) (:documentation "Request that the production system select a production, given a maximum time constraint.")) ;;; SELECT-PRODUCTION [Method] ;;; Description : A number of things have to be done here. It's necessary to ;;; : first check the goal stack, then actually call ACT-R5's ;;; : MATCHING-CYCLE function, then deal with that result and ;;; : either schedule the completion of the cycle or note the ;;; : failure. (defmethod select-production ((cog-mod cognition-module) &optional max-time) (if (null (first (no-output (goal-stack-fct)))) (setf (ps-state cog-mod) :NO-GOAL) (let* ((rt *retrieval-threshold*) (ut *utility-threshold*) (action-latency (matching-cycle max-time)) ) (setf *retrieval-threshold* rt) (setf *utility-threshold* ut) (if action-latency (progn (setf (ps-state cog-mod) :PENDING-EXECUTION) ;; what about time randomization? (queue-command :time action-latency :where :COGNITION :command 'execute-rhs :params (list action-latency) :randomize nil )) (setf (ps-state cog-mod) :FAILED-SELECTION))))) ;;; EXECUTE-RHS [Method] ;;; Date : 01.07.27 ;;; Description : Calls the ACT-R5 function ACTION-CYCLE, idles the CP [which ;;; : will cause a new SELECT-PRODUCTION to be scheduled by the ;;; : Master Process], and then it's necessary to deal with the ;;; : retrieval, if any. (defmethod execute-rhs ((cog-mod cognition-module) (latency number)) (setf *time* (- *time* latency)) ;; back up in time because action-cycle is ;; going to increment *time* and I can't just ;; pass 0 because it needs that latency for ;; parameter learning purposes... (action-cycle latency) (setf (ps-state cog-mod) :IDLE) (when (and *retrieval-scheduler* (not (equal *retrieval-scheduler* (last-retrieval-request cog-mod)))) (setf (last-retrieval-request cog-mod) (copy-tree *retrieval-scheduler*)) ;; have to subtract because the first of *retrieval-schuduler* is the ;; absolute time of the retrieval completion (queue-command :time (- (first *retrieval-scheduler*) (mp-time *mp*)) :where :COGNITION :command 'complete-retrieval :params (list (first *retrieval-scheduler*)) :randomize nil))) ;;; don't really need the time parameter--the MP knows what time it is. (defmethod complete-retrieval ((cog-mod cognition-module) (time number)) (when *retrieval-scheduler* (retrieval-event time))) ;;; -*- mode: LISP; Package: CL-USER; Syntax: COMMON-LISP; Base: 10 -*- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Author : Mike Byrne & Dan Bothell ;;; Copyright : (c)1999-2002 CMU/Rice U./Mike Byrne, All Rights Reserved ;;; Availability: public domain ;;; Address : Rice University ;;; : Psychology Department ;;; : Houston, TX 77251-1892 ;;; : byrne@acm.org ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Filename : device-interface.lisp ;;; Version : 2.1b4 ;;; ;;; Description : File for managing the device interface. ;;; ;;; Bugs : None known. ;;; ;;; Todo : Nothing pending. ;;; ;;; ----- History ----- ;;; 01.09.21 mdb [b2] ;;; : Changed PROCESS-DISPLAY and UPDATE-CURSOR-FEAT to generate ;;; : fewer gentemps. Probably faster, too. ;;; 01.11.11 mdb [b3] ;;; : Added TRACE-MOUSE-P and MOUSE-TRACE slots. ;;; ;;; 02.01.15 Dan [b4] ;;; : Changed the feature test for ACL in the setting ;;; : of the pixels-per-inch veriables so that it works ;;; : in other than ACL 5.0.1. ;;; : I don't think those values even matter however because ;;; : when pm-reset gets called the device interface ;;; : pixels-per-inch gets set to the value of the ;;; : corresponding parameter which defaults to 72, and ;;; : not the current "real" values as defined by the ;;; : system's resolution. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; unless it's MCL define the pixels-per-inch #-:mcl (progn (defvar *pixels-per-inch-x* 72) (defvar *pixels-per-inch-y* 72)) ;;; if it's ACL with the IDE (Windows) then find the true units-per-inch #+:ALLEGRO-IDE (multiple-value-bind (x y) (stream-units-per-inch (screen *system*)) (setf *pixels-per-inch-x* x) (setf *pixels-per-inch-y* y)) ;;;; ---------------------------------------------------------------------- ;;;; ;;; roll in everything that was in "environment-interface" (defvar *actr-enabled-p* t) (defun pm-get-time () "If ACT is in control, return simulated time, else return real time (in ms)." (if *actr-enabled-p* (round (* 1000 (mp-time *mp*))) (get-internal-real-time))) (defun pm-timed-event (time func &rest args) "To schedule an external event at a specific time (in seconds), call this" (setf (sched-q *mp*) (queue-insert (make-instance 'input-queue-entry :time-tag time :destination :EXTERNAL :params (cons func args)) (sched-q *mp*)))) (defun pm-delayed-event (delay func &rest args) "To schedule an external event at some delay (in seconds) after the current time, call this" (setf (sched-q *mp*) (queue-insert (make-instance 'input-queue-entry :time-tag (+ (mp-time *mp*) delay) :destination :EXTERNAL :params (cons func args)) (sched-q *mp*)))) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Base DEVICE-INTERFACE class and some quicky methods. (defclass device-interface () ((pixels-per-inch :accessor ppi :initarg :ppi :initform (/ (+ *pixels-per-inch-x* *pixels-per-inch-y*) 2.0)) (viewing-distance :accessor viewing-distance :initform 15.0) (device :accessor device :initform nil) (key-closure-time :accessor key-closure-time :initform 0.010) (microphone-delay :accessor :microphone-delay :initform 0.100) (keyboard :accessor keyboard :initform (make-instance 'virtual-keyboard)) (speech-hook :accessor speech-hook :initform nil) (device-speech-p :accessor device-speech-p :initform nil) (with-cursor-p :accessor with-cursor-p :initform nil) (input-q :accessor input-q :initform nil) (device-hook :accessor device-hook :initform nil) (mouse-fitts-coeff :accessor mouse-fitts-coeff :initform 0.1) (show-focus-p :accessor show-focus-p :initarg :show-focus-p :initform nil) (trace-mouse-p :accessor trace-mouse-p :initarg :trace-mouse-p :initform nil) (mouse-trace :accessor mouse-trace :initarg :mouse-trace :initform nil) )) (defgeneric angle->pixels-mth (devin angle) (:documentation "Determine the number of pixels subtending a visual angle.")) (defmethod angle->pixels-mth ((devin device-interface) (angle number)) (round (* (* (viewing-distance devin) (tan (deg->rad angle))) (ppi devin)))) (defgeneric pixels->angle-mth (devin pixels) (:documentation "Determine the amount of visual angle subtended by .")) (defmethod pixels->angle-mth ((devin device-interface) (pixels number)) (rad->deg (atan (/ (/ pixels (ppi devin)) (viewing-distance devin))))) (defgeneric find-viewing-dist-mth (devin angle pixels) (:documentation "Given the number of pixels an angle subtends, what's the viewing distance?")) (defmethod find-viewing-dist-mth ((devin device-interface) angle pixels) (floor (/ pixels (* (tan (deg->rad angle)) (ppi devin))))) ;;;; ---------------------------------------------------------------------- ;;;; ;;;; Interacting with the Master Process (defmethod reset-device ((devin device-interface)) (setf (input-q devin) nil) (setf (mouse-trace devin) nil)) (defmethod reset-module ((devin device-interface)) (reset-device devin)) (defgeneric update-device (devin time) (:documentation "Update the device at