Working Within a FileTopUsing a Control ShellAdding DimensionsGoTo Top

Adding Dimensions

A central concept in GBBopen is dimensionality. Dimensional abstraction of space instances, unit instances, and proximity-based retrieval patterns is used to provide a semantically meaningful separation of blackboard-repository storage mechanisms from system and application code. This separation provides flexibility in developing and evolving complex blackboard applications and allows GBBopen to change storage and search strategies and optimizations dynamically.

Each space instance can be created as a conceptual hyper-dimensional volume. Unit instances occupy multidimensional extent based on their attributes. The location of a unit instance within the space instance is determined by the intersection of the space instance's dimensionality and the unit instance's dimension values.

GBBopen supports three types of dimensions:

Determining the dimensionality of space and unit instances is an important part of designing a blackboard application.

In this exercise, we will redefine the location unit class to have two ordered dimensions, x and y, that represent Euclidean positions on a two-dimensional plane. Then we will create a two-dimensional known-world space instance, create some location unit instances on the known-world, and retrieve the instances based on their two-dimensional positions.


This exercise shows you how to:


Prerequisites

Step 1: Add dimensions to the location unit class

Edit your tutorial-example.lisp file, and change the location unit-class definition as follows:

  (define-unit-class location ()
    (x y)
    (:dimensional-values
      (x :point x)
      (y :point y)))
In this tutorial, we will highlight code additions and changes using a black font.

The :dimensional-values unit-class option specifies that location unit instances have two ordered dimensions, x and y, and that the value of each dimension will be a single numeric value obtained from the slots x and y, respectively.

Because we chose to use the same name for each dimension and its associated slot value, our :dimensional-values option might appear to be double-talk. We could have defined our class as:

  (define-unit-class location ()
    (x-slot y-slot)
    (:dimensional-values
      (x :point x-slot)
      (y :point y-slot)))
which clarifies the semantics of the :dimensional-values option. Often, however, it is most convenient to use the same name for a slot and the dimension associated with the slot's value, so we will stick with our original definition.

Step 2: Compile and load the new definition

We could compile and load the entire tutorial-example.lisp file just as we did in the last exercise. However, as we develop our application it can be convenient to compile and load (and debug) each form as we write it. Your Common Lisp development environment should provide this capability. In SLIME, the command to compile the current top-level form is C-c C-c. In Allegro's ELI, the command is C-c C-x. Try compiling and loading just the new location unit-class definition.

Step 3: Make a location unit instance

Let's test our new location unit class definition by making an instance. Enter the following form in the REPL:

  gbbopen-user> (defparameter ui (make-instance 'location :x 40 :y 60))
  ui
  gbbopen-user>
and display its description:
  gbbopen-user> (describe-instance ui)
  Location #<location 1>
    Instance name: 1
    Space instances: None
    Dimensional values:
      x:  40
      y:  60
    Non-link slots:
      x:  40
      y:  60
    Link slots: None
  gbbopen-user>
Note the dimensional values for the x and y dimensions.

Step 4: Make the known-world space instance

Create the known-world space instance by evaluating:

  gbbopen-user> (defparameter si (make-space-instance '(known-world)))
  si
  gbbopen-user>

Step 5: Add the unit instance to the space instance

Now, add the location unit instance to the space instance:

  gbbopen-user> (add-instance-to-space-instance ui si)
  Warning: In add-instance-to-space-instance: #<location 1>
           does not share any dimensions with space instance 
           #<standard-space-instance (known-world)>.
  #<location 1>
  gbbopen-user>

GBBopen has warned us that our location unit instance does not have any dimensions in common with the known-world space instance (because we didn't specify any dimensions for the known-world). GBBopen dutifully added the unit instance, as shown by describe-blackboard-repository:

  gbbopen-user> (describe-blackboard-repository)
  
  Space Instance                Contents
  --------------                --------
  known-world                   1 instance (1 location)

  Unit Class                    Instances
  ----------                    ---------
  location                              1
  standard-space-instance               1
                                ---------
                                        2 instances
  gbbopen-user>
but we cannot perform dimension-based retrieval of our location unit instance on the known-world.

Step 6: Create a dimensioned known-world

Let's delete the known-world and create another one—this time with x and y dimensions:

  gbbopen-user> (delete-space-instance si)
  #<deleted-unit-instance standard-space-instance (known-world)>
  gbbopen-user> (setf si (make-space-instance '(known-world)
              :dimensions '((x :ordered) (y :ordered))))
  #<standard-space-instance (known-world)>
  gbbopen-user>
We have specified x and y as ordered dimensions, making the known-world a two-dimensional Euclidean plane. (Flatlanders would be proud!)

Verify the dimensionality of the known-world space instance by evaluating:

  gbbopen-user> (describe-space-instance si)
  Standard-space-instance #<standard-space-instance (known-world)>
    Allowed unit classes: t
    Dimensions:
      (x :ordered)
      (y :ordered)
  gbbopen-user>

Step 7: Add the location unit instance to the new known-world

Add the location unit instance to the new known-world space instance:

  gbbopen-user> (add-instance-to-space-instance ui si)
  #<location 1>
  gbbopen-user>
The dimension warning is gone.

Step 8: Adding every location to the known-world automatically

Up to this point, we have used add-instance-to-space-instance to add each location unit instance to the known-world space instance. We can tell GBBopen to automatically add new unit instances to one or more space instances by using the :initial-space-instances class option in define-unit-class.

Add the following :initial-space-instances class option to the location unit-class definition in your tutorial-example.lisp file:

  (define-unit-class location ()
    (x y)
    (:dimensional-values
      (x :point x)
      (y :point y))
    (:initial-space-instances (known-world)))

Compile and load the new location unit-class definition.

Step 9: Create more location unit instances

Let's test our new location unit class definition by making another instance. Enter the following form in the REPL:

  gbbopen-user> (make-instance 'location :x 70 :y 30)
  #<location 2>
  gbbopen-user>
and confirm that the new location is on the known-world:
  gbbopen-user> :dsbb
  
  Space Instance                Contents
  --------------                --------
  known-world                   2 instances (2 location)

  Unit Class                    Instances
  ----------                    ---------
  location                              2
  standard-space-instance               1
                                ---------
                                        3 instances
  gbbopen-user>
Here we used GBBopen's :dsbb REPL command, which is equivalent to evaluating (describe-blackboard-repository). Describing the repository is a useful check that our unit instances and space instances are being created and deleted as intended.

Now, let's populate the known-world with a few more locations:

  gbbopen-user> (make-instance 'location :x 20 :y 20)
  #<location 3>
  gbbopen-user> (make-instance 'location :x 25 :y 25)
  #<location 4>
  gbbopen-user> (make-instance 'location :x 20 :y 30)
  #<location 5>
  gbbopen-user>
and verify that they are all on the known-world:
  gbbopen-user> :dsbb
  
  Space Instance                Contents
  --------------                --------
  known-world                   5 instances (5 location)

  Unit Class                    Instances
  ----------                    ---------
  location                              5
  standard-space-instance               1
                                ---------
                                        6 instances
  gbbopen-user>

Step 10: Dimensional retrieval

We have seen how we can use find-instances to retrieve all location unit instances from the known-world:

  gbbopen-user> (find-instances 'location '(known-world) :all)
  (#<location 5> #<location 4> #<location 3> #<location 2> #<location 1>)
  gbbopen-user>

Now that we have added x and y dimensions to location unit instances and to the known-world, we can retrieve location unit instances using dimensional patterns. For example, let's retrieve the unit instance positioned at (20,20):

  gbbopen-user> (find-instances 'location '(known-world)
                  '(and (= x 20) (= y 20)))
   (#<location 3>)
  gbbopen-user>

We can use describe-instance to verify that we found the desired location unit instance:

  gbbopen-user> (find-instances 'location '(known-world)
                  '(and (= x 20) (= y 20)))
   (#<location 3>)
  gbbopen-user> (describe-instance (first *))
  Location #<location 3>
    Instance name: 3
    Space instances: ((known-world))
    Dimensional values:
      x:  20
      y:  20
    Non-link slots:
      x:  20
      y:  20
    Link slots: None
  gbbopen-user>
Note that we used Common Lisp's REPL * variable that is always set to the value returned by evaluating the last REPL expression (in this case, the result of find-instances).

Step 11: Customize the display of location unit instances

It would be convenient if we could easily see the coordinates of location unit instances without having to describe them. Fortunately, GBBopen makes this is easy to do by providing the print-instance-slots generic function that allows us to extend how Common Lisp's print-object displays location unit instances.

Add the following print-instance-slots method after the location unit-class definition in your tutorial-example.lisp file:

  (define-unit-class location ()
    (x y)
    (:dimensional-values
      (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))))
The method first performs a (call-next-method) to produce the initial printed representation of the location unit instance. It then checks that both x and y slots are bound and, if so, writes the location's coordinates to the output stream. Checking that the slots are bound is necessary to avoid generating an error in the print-instance-slots if the slots have not been given a value. You should always perform this safety check in any print-instance-slots methods. (GBBopen provides the generic function print-instance-slot-value for use in safely displaying a slot value in print-instance-slots methods. We did not use print-instance-slot-value here, as our print-instance-slots method presents two slots, x and y, formatted together.)

Now, compile and load the print-instance-slots method and try it out:

  gbbopen-user> (find-instances 'location '(known-world)
                  '(= (x y) (20 20)))
   (#<location 3 (20 20)>)
  gbbopen-user>

Note that this time we used a two-dimensional (x,y) retrieval pattern rather than the conjunction of two one-dimensional patterns that we used previously. The two patterns are equivalent, but often a higher-dimensional pattern may be more convenient than a conjunction. Also note that we can see immediately that we retrieved the desired location.

Step 12: More dimensional retrievals

Let's try some additional dimensional retrievals. First, find all location unit instances with an x position of 20:

  gbbopen-user> (find-instances 'location '(known-world)
                  '(= x 20))
   (#<location 5 (20 30)> #<location 3 (20 20)>)
  gbbopen-user>
Find all location unit instances whose x and y coordinates are less than or equal to 25:
  gbbopen-user> (find-instances 'location '(known-world)
                  '(<= (x y) (25 25)))
  (#<location 4 (25 25)> #<location 3 (20 20)>)
  gbbopen-user>
Find all location unit instances whose x coordinates are between 0 and 40 (inclusive) and whose y coordinates are between 60 and 100 (inclusive):
  gbbopen-user> (find-instances 'location '(known-world)
                  '(within (x y) ((0 40) (60 100))))
  (#<location 1 (40 60)>)
  gbbopen-user>
Find all location unit instances whose coordinates are not within the above region:
  gbbopen-user> (find-instances 'location '(known-world)
                  '(not (within (x y) ((0 40) (60 100)))))
  (#<location 5 (20 30)> #<location 4 (25 25)> #<location 3 (20 20)>
   #<location 2 (70 30)>)
  gbbopen-user>

Step 13: Change a dimensional value

Recall that we assigned location 1 to the global variable ui:

  gbbopen-user> ui
  #<location 1 (40 60)>
  gbbopen-user>
and we can retrieve it by its x and y coordinates:
  gbbopen-user> (find-instances 'location '(known-world)
                  '(= (x y) (40 60)))
  (#<location 1 (40 60)>)
  gbbopen-user>

Let's change its x position to 80 and try retrieving it again:

  gbbopen-user> (setf (x-of ui) 80)
  80
  gbbopen-user> (find-instances 'location '(known-world)
                  '(= (x y) (40 60)))
  nil
  gbbopen-user>
It has moved on the known-world. As expected, the location 1 unit instance is now at (80, 60):
  gbbopen-user> (find-instances 'location '(known-world)
                  '(= (x y) (80 60)))
  (#<location 1 (80 60)>)
  gbbopen-user>
Also note that the textual representation of location 1 shows the new x slot value.


The GBBopen Project


Working Within a FileTopUsing a Control ShellAdding DimensionsGoTo Top