NeoClassic Knowledge Representation System Tutorial (Part 1)

Copyright AT&T 1996

Here is the text version


This file contains your first NeoClassic assignment. With the help of the explanations and the code in this and other files you will build a knowledge base (KB) of wines and food. The more advanced lessons will show you how to use this knowledge base to support various reasoning tasks, such as how to select appropriate wines for different kinds of food or how to put together an appropriate meal. The goal of these assignments is to familiarize you with the NeoClassic knowledge representation system, and to provide an idea of the types of reasoning and knowledge representation that can be done using NeoClassic.

NeoClassic is a fairly complex system that provides logical completions which are sometimes non-obvious. This first assignment is intended to be a hands-on tutorial introduction to NeoClassic. It is intentionally fairly mechanical; the purpose is to accustom you to the system and command language. You will practice using useful functions to modify and inspect the KB. In later assignments you will go on to explore some of the subtleties of NeoClassic. To better understand this first module of the tutorial, you should read the ``Concepts'' section under "NeoClassic Conceptual Building Blocks" in the NeoClassic Reference Manual. All the functions mentioned in this file are documented in the Reference Manual.


We will use the following conventions to make our definitions clearer:

KB entities:

NeoClassic system: C++:


The following text will tell you what to do at each point. You will be asked to type in some concept definitions and to use several NeoClassic functions to inspect the KB. In some cases you will be asked to write your own definitions. For some of these problems, we have provided answers, although you are encouraged to try to solve them yourself before giving up; for others you are on your own. Failing to solve a problem for which no solution has been provided will not critically impair continuing the work that follows the problem.

You should follow the directions given by the text with respect to typing in definitions or function calls. You should also type in any lines of NeoClassic code that are prefixed by >>>.

You should also follow directions with respect to the "session logs" that log your work. Every time a written answer is required, remember to comment it using ";;", so that it will be easily recognizable in the session log.

Finally, note that the assignment has been broken up into modules, so that you don't have to do it all in one session. Each module will start with a brief recapitulation of past work and instructions for files to load in order to "recover state". Your life will be easier if you try to get through a whole module before you take a break.


You will need to turn in evidence that you have gone through the assignment, i.e., log file(s) that record you going through the tutorial.

In general: To record your work in a file use a shell in EMACS. You can get this by typing meta-X shell. When you are done, or while you are still using it, you can write the session log to a file by simply saving it. Be sure to save your session log before you exit EMACS.

CAREFUL!! Use different shell filenames for different files, or you will overwrite your previous work.


At several points in the assignment you will be told to load files. Some of these files contain NeoClassic definitions for building the KB. We have provided these because the KB is quite large. Other files can be used to "clean-up state", i.e., to remove mistakes you may have made in working through the assignment while insuring that all the needed definitions are loaded. These "recovery" files can also be used to resume working on the assignment if you don't get it done in one session.

You will be using the function "readFile" to load files. You will need to put in the full path of the file that you are loading. That file might be in the Helper or Recover sub-directories relative to "topdir", the default top-level directory or the one that you have established. (See the assignment handout). When you see a filename like "<topdir>/Helper/<filename>", or "<topdir>/<filename>" substitute in the right name of the top-level directory you're using (that is the directory that you put the tutorial files into --- in fact the directory that this file is in).

Here we go. Change directories, to the NeoClassic directory, if necessary. If you put the directory in your path, you can type the command, "neoclassic" from any directory. This command will start up the NeoClassic interpreter.

Make sure your work is being recorded in a file starting now.

>>> meta-x shell

Start the NeoClassic interpreter. >>> neoclassic


The function reinitCollection initializes the KB. If you have just loaded NeoClassic, you don't need to call it, but if you have been working in NeoClassic for a while and are loading a new KB or you have made changes to the KB code and are reloading, you need to call it. The function reinitCollection removes all concepts, individuals, roles and rules except for Thing, ClassicThing, HostThing, and a few other built-in concepts. Since concepts cannot be changed within a KB, you will find that when you want to redefine a concept, you will need to re-initialize the KB as well. Note that the syntax of NeoClassic allows you to put the parentheses around the arguments to the function only (as in C or C++) or you can include the function name inside of the parentheses (as in Lisp). However, once you start typing an expression into the interpreter, the syntax must remain in the same mode (C or Lisp). Throughout this tutorial we will use the Lisp mode.

>>> (reinitCollection)


We will now start building the first part of the hierarchy shown in the diagram, classic-thing, i.e., the immediate children of ClassicThing. There are four disjoint primitive concepts at the top level: WINE-PROPERTY, WINE-REGION, WINERY and CONSUMABLE-THING, all in the disjoint grouping "type". Such an organization of concepts is typical: A NeoClassic application will usually have primitives in the highest tiers of the hierarchy.

NOTE: The pictures of the KB do not explicitly show disjoint groupings. You may want to add them by hand, using the notation shown in Figure 1, which is in the section called ``Primitives'' under the section called "Concepts" which is in the "NeoClassic Interface Functionality" section in the NeoClassic Reference Manual.


All concepts, including primitive, disjoint-primitive and non-primitive concepts, have at least one parent. If the parent is not specified in the definition, and the concept does not fit under a more specific concept, then it is classified under one of the built-in concepts, HostThing or ClassicThing.

A non-primitive concept definition specifies both the necessary and sufficient conditions that an individual must satisfy in order to be an instance of that concept. (Necessary conditions are the conditions that an instance of the concept must meet. Sufficient conditions are conditions that only instances of the concept meet; i.e., if an individual satisfies sufficient conditions, then it will be an instance of the concept.) Since you usually will be either unable or unwilling to specify all the sufficient conditions for very general concepts (e.g. WINE), or for concepts that are learned by example, you should be prepared to treat some concepts as primitives. On the other hand, NeoClassic obtains much of its reasoning power from well structured definitions. Whenever you can fully specify a concept by a definition, you should define it as a non-primitive concept. For example, the definition of the concept VEGETARIAN is someone who eats only vegetables. This definition fully specifies the concept. Thus, you should define VEGETARIAN as a non-primitive concept so that any individual that eats only vegetables will automatically be classified as a VEGETARIAN. See "Living with CLASSIC" for useful advice on this matter.

A primitive concept definition (including a disjoint primitive concept definition) specifies the necessary but not the sufficient conditions that an individual must satisfy in order to be an instance of the concept. For instance, later we will introduce a disjoint primitive concept MEAL-COURSE with the roles food and drink, which must have fillers meeting certain conditions. If we know that Meal-Course-134 is a MEAL-COURSE, then we can infer that Meal-Course-134 will have its food and drink roles filled by individuals that meet these conditions. But, since a MEAL-COURSE is primitive, we cannot infer that an individual is a MEAL-COURSE if its food and drink role fillers meet the conditions.

The effect of making a concept P a primitive is that, even if a concept C has compatible restrictions or an individual Ind happens to satisfy P's restrictions, the system will not place C or Ind under P unless it is explicitly or implicitly told to do so. An example of implicitly telling the system to do this is: Concept R has parent P and Concept C has parent R.

Now, using the functions "createDisjointGroup" and "createConcept", create the disjoint primitive concepts WINE-PROPERTY, CONSUMABLE-THING, WINERY and WINE-REGION as children of ClassicThing within a disjoint grouping called classic-thing-type.


The four disjoint concepts that you have just defined create a partition or disjoint grouping called "classic-thing-type" in the space of concepts under ClassicThing. The four concepts are disjoint in the sense that any individual belonging to any one of them cannot also belong to any of the others. (See the ``NeoClassic User's Guide: Version 0.7'' for other examples and more explanation of disjoint primitives and disjoint groupings.)

The following statements attempt to violate the disjoint grouping. Try them and see what happens. They should fail to create anything, and return an error message.

; an incoherent concept
>>> (createConcept C (and WINE-PROPERTY WINE-REGION))

; an inconsistent individual
>>> (createIndividual Ind (and WINERY WINE-REGION))


Now let's practice using some of the commands NeoClassic provides for inspecting the knowledge base. This will let you browse the information that has already been created for purposes of this exercise. The NeoClassic functions that use concepts, individuals, and roles as arguments do not expect you to use a particular format for indicating these arguments, except in cases where the function can take more than one type of argument in a particular place and the argument given can be more than one of those types. In that case, the argument is prefaced with the type of argument it is, as a function name. For instance, if you want to get the comment associated with the role "color" and there is also a concept called "color" you would need to say "(getComment (role color))". If you have not used the same name for a concept, individual, or role you will not need to be concerned with this.
(Note: NeoClassic does distinguish between upper and lower case.) The functions used to specify particular arguments are role, symbolToRole, concept, symbolToConcept, symbolToClassicConcept, classic-concept, symbolToHostConcept, host-concept, symbolToRule, rule, classic-individual and symbolToClassicIndividual.

Try typing:
>>> (concept WINERY)

To print the concept WINE-REGION, type the following:
>>> (printInfo WINE-REGION)

Here is the output you should see:

======================== ClassicConcept ========================
|| Name:                   WINE-REGION
||    Primitive:           {classic-thing-type}
||    Primitives:          {WINE-REGION}
||    Rules:               {}
|| Ingredients:
||    Told Parents:        {ClassicThing}
|| Taxonomy:
||    Parents:             {ClassicThing}
||    Children:            {}
||    DirectInstances      {}
|| Soup:
||    Primitive Ancestors: {}
||    One of:              {}
||    RoleRestrictions:    {}
||    Tests:               {}
||    Incoherent?          false
The output is easy to interpret. If you want to see only a specific detail about an object, use the specific function for that detail.

Try typing:

>>> (getPrimitive WINE-REGION)

Remember how we defined WINE-REGION:

(createConcept WINE-REGION ClassicThing classic-thing-type)

Here the response includes information that this is a disjoint primitive on parent ClassicThing.

More complex concepts will have correspondingly more information associated with them. We will see some examples below.

You can see what the hierarchy looks like so far, without printing out ALL the information associated with a concept or individual, by using the following NeoClassic functions, which are documented in the NeoClassic Reference Manual under the section on ``Taxonomy Retrievals'' which is under the section on Concepts which is in the "NeoClassic Interface Functionality" section.

To get some practice in using the above functions to browse through a Classic hierarchy, try the following:


Now let's define some more disjoint concepts in our KB. We'll work on the concepts below CONSUMABLE-THING in
classic-thing, that is, EDIBLE-THING and POTABLE-LIQUID. These should be disjoint concepts under CONSUMABLE-THING. Following the pattern we used above for CONSUMABLE-THING, WINE-PROPERTY, etc., define the two concepts EDIBLE-THING and POTABLE-LIQUID as disjoint primitives under CONSUMABLE-THING within a disjoint grouping called consumable-thing-type (remember, you will need to provide unique names for each disjoint grouping).

You can look up the definitions of the primitive concepts defined up to now in ``<topdir>/Recover/after-consumable-thing.Neo''


We switch our attention now to subclasses of the primitive concept WINE-PROPERTY, as shown at the top of and in more detail in wine-property.

The first thing we'll do is create the four concepts WINE-COLOR, WINE-BODY, WINE-FLAVOR and WINE-SUGAR, under WINE-PROPERTY. These are not primitive concepts. They will be defined as enumerative concepts, i.e., concepts that are totally defined by their parents and a set of user-defined direct instances. Enumerative concepts are created using the oneOf restriction which is explained in the section entitled "Building Classic Descriptions" in the ``NeoClassic User's Guide: Version 0.7''.

We want each of the new concepts to be a WINE-PROPERTY whose remaining definition is simply to be one of the instances listed under it in wine-property.

So try to define the concept:

WINE-COLOR: Is a WINE-PROPERTY and is one of White, Rose, or Red.

You might be surprised to see the result of using the getInstances command on WINE-COLOR. Use the command to see what the instances of WINE-COLOR are.

You should have gotten an empty set. This happens because the individuals White, Rose, and Red did not exist prior to the definition of WINE-COLOR. NeoClassic created these individuals, but since nothing else is specified about them, they are just classified as ClassicThings. If WINE-COLOR was defined as "(oneOf White Rose Red)", and did not include the primitive concept WINE-PROPERTY in its definition, the individuals in the oneOf would be classified under it. However, it contains the primitive concept WINE-PROPERTY in its definition, so the WINE-COLOR individuals won't get classified under WINE-COLOR unless we specify that they are instances of WINE-COLOR or WINE-PROPERTY. The command "addToldInformation" can be used to do this, e.g.

>>> (addToldInformation White WINE-COLOR)

Note that individuals are one thing that NeoClassic will automatically create if they haven't been mentioned before, unlike concepts and roles which have to be defined before they are used. NeoClassic can create an individual without knowing anything about it since information can be added to it at a later time. In contrast, role and concept definitions are not modifiable.

Now create and populate the following concepts:

You can use the KB navigation commands to make sure all the individuals are properly classified.

Now that your knowledge base is populated with some individuals, you can browse the individuals, as well as the concepts. Some useful functions for retrieving information about individuals are:

Also, remember the function, getDirectInstances, which retrieves information about a concept. Try the following: You can compare your definitions of the WINE-PROPERTY's subclasses with the ones in the file ``<topdir>/Recover/after-wine-properties.Neo''. Load this file if you want to recover from errors you made in your definitions.


Now that we have created the concept WINE-PROPERTY and its subconcepts, we can use them to define the concept WINE.

We want to define WINE as a POTABLE-LIQUID with attributes color, body, flavor, sugar and grape. In order to do this, we need to create the necessary attributes first. Read the ``Roles'' section (in the part called "NeoClassic Conceptual Building Blocks") of the NeoClassic Reference Manual for more information about roles.

For example, let's create the attribute `color'

>>> (createRole color true)

Use the same construct to create the attributes 'body', 'flavor', 'sugar' and 'grape'.

Now everything we need to define the concept WINE is in the KB. We want to define a WINE as a POTABLE-LIQUID that has:

  1. exactly one 'color', which is a WINE-COLOR.
  2. exactly one 'body', which is a WINE-BODY.
  3. exactly one 'flavor', which is a WINE-FLAVOR.
  4. exactly one 'sugar', which is a WINE-SUGAR.
  5. exactly one 'grape', which is an EDIBLE-THING.
Remember that NeoClassic provides constructs for defining number restrictions on roles. For further information about this read the section called "Building Classic Descriptions" (section 2.1 in version 0.7 of the ``NeoClassic User's Guide: Version 0.7''). For example, to specify that WINE has exactly one color, you may combine an "atLeast 1" and "atMost 1" number restriction. But if a role is defined to be an attribute, an "atMost 1" restriction is automatically established, while the default atLeast restriction of an attribute and a regular role is set to 0. Therefore, if you have defined color, body, flavor, sugar and grape as attributes, only atLeast number restrictions must be specified in the definition of the WINE concept.

Also remember that when you state that there is at most one filler for a role and that role has a particular restriction, all (one) of those fillers have that particular restriction.

Define the concept WINE now.

To look at the newly defined concept, use functions to get told and derived information on that concept with different roles.

What difference do you notice between the description of the Role Restrictions in the derived information and the one in the told information? Why does the difference exist? Comment out your answer using semi-colons (;;), so that it will be easily recognizable in your log file.


At this point, we want to add to our knowledge base the concept of MEAL-FOOD which is an EDIBLE-THING that can be served during a meal. Instances of MEAL-FOOD can be, for example, Pasta or Fries, while Flour is an EDIBLE-THING which is not a MEAL-FOOD, because raw flour will never be served during a meal.

The purpose of the knowledge base that we are building is to plan meals in which the foods and wines that are served go together. Thus, the information about which wines go with a particular food has to be entered at some place in the knowledge base. We will use the role appropriate-drink for this purpose, which will later be used to attach appropriate drinks to each MEAL-FOOD in the knowledge base.

Create the role appropriate-drink, and define the concept MEAL-FOOD as:

  2. having at least 1 appropriate-drink
  3. having all appropriate-drinks be WINEs.
If you are in trouble, you can find the definitions of WINE and MEAL-FOOD in ``<topdir>/Recover/upto-meal-food.Neo''.

The remainder of the hierarchy under MEAL-FOOD is shown in `edible-thing'. It's not very interesting, just many disjoint concept definitions. If you want to see some meal definitions, you may look at the following file, but PLEASE DO NOT load it: ``<topdir>/Helper/meal-foods.Neo''.


Now let's see how the restrictions defined on the roles of the WINE concept influence the creation of WINE individuals.

Try to create an individual W1 that is a WINE and has sugar Dry.

Notice what happens when you try to create a new individual W2 that is a WINE and has grape semillon and riesling. Remember that we defined the grape role to be an attribute, meaning that there could be only one grape per wine. NeoClassic reminds you of this in the error message by creating a new individual (which you can examine) with its boolean Inconsistent value set to true. The error message also states that it occurred along the role-path "grape", pointing out that the new value for the role does not work. Since the object could not be created it was not one that you can access by its name. You can access any object that was created by the most recent command by typing "$". So, if you want to print information about this inconsistent object you can type "(printInfo $)". You can also access the object returned by the command entered just before the previous one by typing "$$", and the one before that by typing "$$$."

Create W2 again, this time choosing semillon as the only filler. NeoClassic does not complain this time, and W2 is created (make sure of this by printing W2, using printInfo).

Are you surprised about this? WINE has been defined as having at least one color, one flavor, one sugar and one body, but no fillers for these attributes have been assigned to W2. Why did NeoClassic create W2 without complaining?

If you have problems answering this question, watch how NeoClassic complains when you try to type:

>>> (closeRole W2 color)

Insert in your shell file an explanation of what happened here. (Remember to comment out your explanation.)

Write your shell file out with a name now.


You can go directly to Part 2 or you may want to go back to the main NeoClassic Tutorial page.