![]() | ![]() | ![]() | Making Connections | ![]() |
We finally did some walking in the last exercise and learned how to display
the location
tutorial-example.lisp
(in-package :gbbopen-user)
(define-unit-class location ()
(time
x y)
(:dimensional-values
(time :point time)
(x :point x)
(y :point y))
(:initial-space-instances (known-world)))
(defmethod print-instance-slots ((location location) stream)
(call-next-method)
(when (and (slot-boundp location 'x)
(slot-boundp location 'y))
(format stream " (~s ~s)"
(x-of location)
(y-of location))))
;;; ====================================================================
;;; Startup KS
(defun startup-ks-function (ksa)
(declare (ignore ksa))
;; Create an initial location unit instance at (0,0):
(make-instance 'location :time 0 :x 0 :y 0))
(define-ks startup-ks
:trigger-events ((control-shell-started-event))
:execution-function 'startup-ks-function)
;;; ====================================================================
;;; Initializations (run at Agenda Shell startup)
(defun initializations (event-name &key &allow-other-keys)
(declare (ignore event-name))
;; Clean up any previous run:
(delete-blackboard-repository)
;; Make a new known-world space instance:
(make-space-instance
'(known-world)
:dimensions (dimensions-of 'location)))
(add-event-function 'initializations 'control-shell-started-event
;; Initializations should be done first!
:priority 100)
;;; ====================================================================
;;; Random-walk KS
(defun add-linear-variance (value max-variance)
;;; Returns a new random value in the interval
;;; [(- value max-variance), (+ value max-variance)]
(+ value (- (random (1+ (* max-variance 2))) max-variance)))
(defun random-walk-ks-function (ksa)
;;; Move to the next (random) location in the world
(let* ((trigger-instance (sole-trigger-instance-of ksa))
;; The new time is one greater than the stimulus's time:
(time (1+ (time-of trigger-instance))))
(cond
;; If the maximum time value (75) is reached, tell the user we've
;; walked too long:
((>= time 75) (format t "~2&Walked too long.~%"))
(t ;; The new location is +/- 10 of the stimulus's location:
(let ((x (add-linear-variance (x-of trigger-instance) 10))
(y (add-linear-variance (y-of trigger-instance) 10)))
(cond
;; Check that the new location is within the known-world
;; boundaries. If so, create the new location instance:
((and (<= -50 x 50) (<= -50 y 50))
(make-instance 'location
:time time
:x x
:y y))
;; Otherwise, tell the user that we've walked too far away:
(t (format t "~2&Walked off the world: (~d, ~d).~%" x y))))))))
(define-ks random-walk-ks
:trigger-events ((instance-created-event location))
:rating 100
:execution-function 'random-walk-ks-function)
:agenda-shell-user
In the last exercise, we used locationlocation
A link is a bidirectional relationship between two unit instances that is implemented by two pointers. From the perspective of a particular unit instance, each link consists of an outgoing, or direct, pointer to another unit instance and an incoming, or inverse, pointer that is stored in unit instance pointed to by the direct pointer. GBBopen automatically maintains the bidirectional-link consistency of these pointers when creating new links, deleting existing links, or deleting unit instances. Links remove the possibility of “one-sided” relationships or “dangling” pointers to deleted unit instances.
Edit the locationtutorial-example.lispnext-locationprevious-locationlocation
(define-unit-class location ()
(time
x y
(next-location
:link (location previous-location :singular t)
:singular t)
(previous-location
:link (location next-location :singular t)
:singular t))
(:dimensional-values
(time :point time)
(x :point x)
(y :point y))
(:initial-space-instances (known-world)))
Each link-slot specification is a list whose first element is the name of the
link slot. This is followed by the link slot option :link and a
concise specification of the inverse link slot associated with that link slot.
In this case, the next-location/previous-locationlocation
Links can be many-to-many, many-to-one, one-to-many, or one-to-one. In this
case, the next-location/previous-location:singular t:singular tlocation
...
(next-locations
:link (location previous-location :singular t)))
(previous-location
:link (location next-locations)
:singular t)
...
We've followed the natural GBBopen convention of giving singular link slots a
singular name (such as previous-locationnext-locations:singular option is
associated with the previous-locationprevious-locationprevious-locationnext-locations
The concise inverse-link-slot specification supplied by the :link slot
option provides a “double entry” redundancy that is useful when links are
between instances of different unit classes, as the link can be understood by
viewing either class definition. The redundancy also helps GBBopen recognize
inconsistencies in link specifications. The function
tutorial-example.lispnext-locationprevious-location
gbbopen-user> (check-link-definitions) ;; All link definitions are consistent. t gbbopen-user>GBBopen reports that all link definitions are consistent.
Suppose that we had forgotten to add the previous-locationlocationlocationtutorial-example.lisp#+ignoreprevious-location
(define-unit-class location ()
(time
x y
(next-location
:link (location previous-location :singular t)
:singular t)
#+ignore
(previous-location
:link (location next-location :singular t)
:singular t))
(:dimensional-values
(time :point time)
(x :point x)
(y :point y))
(:initial-space-instances (known-world)))
The #+ignoreignore is not an element of the feature list
*features*ignore is never added to
*features*#+ignore is a handy mechanism for
temporarily “commenting out” a single form.
Compile the now-defective definition (using C-c C-cC-c C-x
gbbopen-user> (check-link-definitions)
Warning: The inverse of link slot next-location in unit class location
refers to link slot previous-location which is not present in
unit class location.
nil
gbbopen-user>
As expected, GBBopen alerts us to the problem.
Remove the #+ignore:singular tnext-location
(define-unit-class location ()
(time
x y
(next-location
:link (location previous-location) ; :singular t)
:singular t)
#+ignore
(previous-location
:link (location next-location :singular t)
:singular t))
(:dimensional-values
(time :point time)
(x :point x)
(y :point y))
(:initial-space-instances (known-world)))
Compile the again-defective definition (using C-c C-cC-c C-x
gbbopen-user> (check-link-definitions)
Warning: Link slot next-location in unit class location incorrectly
declares its inverse link slot previous-location in unit
class location as not singular.
nil
gbbopen-user>
Once again, GBBopen has alerted us to the problem.
Restore the :singular tnext-location
(define-unit-class location ()
(time
x y
(next-location
:link (location previous-location) ; :singular t)
:singular t)
(previous-location
:link (location next-location :singular t)
:singular t))
(:dimensional-values
(time :point time)
(x :point x)
(y :point y))
(:initial-space-instances (known-world)))
Then recompile and recheck link consistency:
gbbopen-user> (check-link-definitions) ;; All link definitions are consistent. t gbbopen-user>
Let's use our newly defined
next-location/previous-locationlocationrandom-walk-ks-functiontutorial-example.lisp:previous-location
(defun random-walk-ks-function (ksa)
;;; Move to the next (random) location in the world
(let* ((trigger-instance (sole-trigger-instance-of ksa))
;; The new time is one greater than the stimulus's time:
(time (1+ (time-of trigger-instance))))
(cond
;; If the maximum time value (75) is reached, tell the user we've
;; walked too long:
((>= time 75) (format t "~2&Walked too long.~%"))
(t ;; The new location is +/- 10 of the stimulus's location:
(let ((x (add-linear-variance (x-of trigger-instance) 10))
(y (add-linear-variance (y-of trigger-instance) 10)))
(cond
;; Check that the new location is within the known-world
;; boundaries. If so, create the new location instance:
((and (<= -50 x 50) (<= -50 y 50))
(make-instance 'location
:time time
:x x
:y y
:previous-location trigger-instance))
;; Otherwise, tell the user that we've walked too far away:
(t (format t "~2&Walked off the world: (~d, ~d).~%" x y))))))))
Compile the random-walk-ks-functionC-c C-cC-c C-x
gbbopen-user> (start-control-shell) ;; Control shell 1 started Walked off the world: (55, 35). ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 66 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>
Let's describe a couple of locationlocation
gbbopen-user> (describe-instance (find-instance-by-name 1 'location))
Location #<location 1 (0 0)>
Instance name: 1
Space instances: ((known-world))
Dimensional values:
time: 0
x: 40
y: 60
Non-link slots:
time: 0
x: 40
y: 60
Link slots:
next-location: #<location 2 (-10 10)>
previous-location: nil
gbbopen-user>
Note that the next-locationlocation
gbbopen-user> (describe-instance (find-instance-by-name 2 'location))
Location #<location 2 (-10 10)>
Instance name: 2
Space instances: ((known-world))
Dimensional values:
time: 1
x: -10
y: 10
Non-link slots:
time: 1
x: -10
y: 10
Link slots:
next-location: #<location 3 (-6 19)>
previous-location: #<location 1 (0 0)>
gbbopen-user>
The next-locationlocation2
points to the third locationprevious-locationlocationWe can now follow the links to display the random walk:
gbbopen-user> (loop with location = (find-instance-by-name 1 'location)
do (print location)
while (setf location (next-location-of location)))
#<location 1 (0 0)>
#<location 2 (-10 10)>
#<location 3 (-6 19)>
#<location 4 (0 14)>
#<location 5 (-1 14)>
#<location 6 (8 10)>
#<location 7 (17 3)>
#<location 8 (7 -6)>
#<location 9 (10 4)>
#<location 10 (5 -5)>
...
#<location 60 (29 17)>
#<location 61 (31 21)>
#<location 62 (40 23)>
#<location 63 (45 28)>
nil
gbbopen-user>
Let's add a new KS, print-walk-kstutorial-example.lisp
;;; ====================================================================
;;; Print-walk KS
(defun print-walk-ks-function (ksa)
;;; Starting with the initial location instance, print the instance
;;; name and location of the walk
(declare (ignore ksa))
(format t "~2&The random walk:~%")
(let ((instance (find-instance-by-name 1 'location)))
(while instance
(format t "~s (~s ~s)~%"
(instance-name-of instance)
(x-of instance)
(y-of instance))
(setf instance (next-location-of instance))))
;; Tell the Agenda Shell to exit:
':stop)
(define-ks print-walk-ks
:trigger-events ((quiescence-event))
:rating 100
:execution-function 'print-walk-ks-function)
The print-walk-ksquiescence-eventprint-walk-ksrandom-walk-kslocationThe print-walk-ks-functionnext-location/previous-location:stop. The
Agenda Shell checks the value returned by a KS execution function for this
special indicator and, if it is returned, the control shell is exited. If we
did not return :stop, the print-walk-ksquiescence-eventquiescence-event
Let's compile our latest changes and then run our application with the new
print-walk-ks
gbbopen-user> (start-control-shell)
;; Control shell 1 started
Walked off the world: (54, 15).
The random walk:
1 (0 0)
2 (-6 9)
3 (-14 8)
4 (-5 6)
5 (-13 5)
6 (-11 13)
7 (-11 4)
8 (-17 8)
9 (-21 15)
10 (-12 14)
...
35 (40 28)
36 (50 22)
37 (49 12)
38 (47 10)
;; Explicit :stop issued by KS print-walk-ks
;; Control shell 1 exited: 41 cycles completed
;; Run time: 0.01 seconds
;; Elapsed time: 0 seconds
:stop
gbbopen-user>
The GBBopen Project
![]() | ![]() | ![]() | Making Connections | ![]() |