California Driver's Handbook

Overview of the problem

For this tutorial, we will reduce the task of answering questions to answering queries expressed in a formal language and posed against a relevant knowledge base. An electronic version of the California Driver’s Handbook is available at  http://www.dmv.ca.gov/pubs/dl600.pdf. We will formally represent the content from one page in the Driver’s Handbook.  We will then test the representation using a set of sample questions prepared in advance. The development will follow these main steps:

The outcome of this step would normally be a representation of the taxonomy of concepts to be used in the knowledge base (typically in the form of a UML diagram) and a body of functional sentences.  The latter are sentences in a natural language (typically, in English) that distill the operational information contained in the original text. They may also include pseudocode, for more rigor.  Functional sentences must be rigorous, unambiguous, and have the form of if-then rules and factual statements.  Both the taxonomy and the body of functional sentences may be partial and more may be added during the development process.

Perform a failure analysis for any questions that the KB failed to answer. Identify knowledge and inference pieces that prevented the question from being answered. Suggest ways in which one could have made the KB more robust and complete for answering new questions.

This tutorial comes with all the Ergo files that contain the KB statements used in the text. One can load them and try out. These files are linked to from within the text. 

In addition, an extended example that captures a larger portion of the driver's handbook is included -- see the corresponding sidebar link.

Benefits of capturing the driving laws in logic

If we had a complete representation of the driving law, as suggested in this tutorial, one could formulate interesting questions against the knowledge base, which cannot be easily answered without the KB. This would be helpful for individuals to learn how to drive (e.g., pedagogy) as well as for lawmakers concerned with consistency, clarity, and improvement of driving laws and policies. This may also be relevant for self-driving cars because such cars need to make decisions automatically, based on (among other things) what they can deduce from concrete driving situations and the driving laws.  For example:

Answering most of these questions is outside the scope of this tutorial, but they are good candidates for a research project.

Representing the right-of-way laws

As an example, we will consider the part of the Handbook concerning "Laws and Rules of the Road"  of the handbook.  We will analyze sentences from three paragraphs in the aforesaid section and then show the relevant formalization. We have added sentence numbers to the paragraph, for ease of reference.  We should note that California Driver's Handbook keeps changing, so the exact wording may differ slightly from what we are quoting in this tutorial. This possible discrepancy does not matter, however, since our purpose is to illustrate the process of developing a knowledge base for a real-world domain rather than to produce an operational system for a concrete set of laws.

   Laws and rules of the road (Right-of-way rules, General Information):

This paragraph emphasizes the importance of the right of way rules.  In general, sentences in the handbook may provide some operational information that can be represented formally and used by the system as well as non-operational information, which will just be discarded.  Sometimes, entire sentences might be discarded for that reason.  The first sentence above is a case in point. It illustrates the choices one must make about which knowledge or concepts are relevant or practical to capture and which are not.  While concepts such as courtesy and common sense are generally understood by humans (albeit often quite differently), their explicit definition is beyond the current state of the art in AI, and we will decline the challenge of defining these notions here.  Similarly, while "help to promote traffic safety" is motivational to a human, it is not relevant to the goals of capturing knowledge for the purpose of querying.  On the other hand, since the concept of Right of Way is very important for driving and is operationally relevant, we will represent it here even though it is not defined anywhere in the handbook. The second sentence can be viewed as an integrity constraint for driving in that one should never undertake an action that violates the right-of-way of others. The third sentence implicitly states that just because one has the right-of-way, do not assume that it is safe to take a driving action permitted by it.

Wikipedia defines right-of-way as:

The general principle that establishes who has the right to go first is called "the right of way", or "priority".

A common approach as we start to build our knowledge base is to set up an ontology to give context to the terms we are likely to encounter.  By an "ontology" here we mean a taxonomy of concepts used in representing the domain knowledge at hand (the right-of-way in our case) and, for each concept, the properties (or attributes) of the concept along with the type information.

We will show the Ergo definitions as we introduce new facts and rules. First, we define the root class Thing with Event and Entity as two subclasses of Thing.

Event :: Thing.

Entity :: Thing.

Next, we define DrivingSituation and Action as subclasses of Event; the class DrivingEntity as a subclass of Entity; and SpatialEntity as a subclass of Entity to denote a geographical location.

{DrivingSituation,Action} :: Event.

DrivingEntity :: Entity. 

SpatialEntity :: Entity.

Remember that the set notation, such as {DrivingSituation,Action} :: Event, is a shorthand for two facts, DrivingSituation::Event and Action::Event. It is a matter of taste whether to use this shorthand or to spell things out.

Next, we define Drive as a subclass of Action and IllegalDrive as a subclass of Drive. We also introduce the property agent with domain Action and range Entity.

Action[|agent => Entity|].

Drive::Action.

IllegalDrive::Drive.

Recall that the above frame notation [|...|] is used to specify the type information for the properties in a class, which apply (are inherited) to all subclasses and all objects in that class.  Also recall that, from a formal point of view, a property  (such as agent) is a function that maps the objects in its domain-class (Action in this case) to the objects in its range-class (Entity in our example). As we just mentioned, the type information is inherited to subclasses. Formally, this means that the fact  Action[|agent => Entity|]  implies the facts  Drive[|agent => Entity|] and IllegalDrive[|agent => Entity|]. In other words, when restricted to the class  Drive as a domain, the property agent maps Drive-objects to Entity-objects, and similarly for IllegalDrive.

The next two statements below  specify the types of a few more properties. The property participant is defined to have the domain of Event and range of Entity. The same statement also specifies that the property follows has the domain Event and the range Event as well; it will be used to specify when one event immediately follows some other event. The property rightOfWay is defined to have the domain DrivingSituation and range DrivingEntity.

Event[|participant => Entity, follows => Event|].

DrivingSituation[|rightOfWay => DrivingEntity|].

We will have more of the taxonomy and typing statements below and before things become too complicated we bring up the UML diagram of our ontology, which we produced in Step 1, during the design process.

UML diagram for the ontology

To formalize the second sentence from "Laws and rules of the road", we introduce the notion of IllegalDrive. If a driving entity takes a driving action in a situation in which it does not have the right of way, it is an instance of IllegalDrive.  Recall that the first step of the KB development process includes distilling the operational information into a body of rigorous functional sentences. What is the functional sentence corresponding to the concept of IllegalDrive?  Read on.

Functional sentence for the IllegalDrive rule:  If entities E1 and E2 are involved in a driving situation S when E2 has the right of way, but E1 drives first, then E1's drive is illegal.

@!{illegal_drive} 

?D : IllegalDrive  :- 

?D:Drive[agent -> ?E1:Entity, follows -> ?S:DrivingSituation],

?S[participant -> {?E1,?E2}, rightOfWay -> ?E2:Entity],

?E1 != ?E2.

The piece @!{illegal_drive} in the above rule is the Id of that rule. There are many advanced uses for rule Ids in Ergo, but in this tutorial they are mainly used as a way of referencing the different rules in the text.  It is also appropriate to remind that the frame syntax encourages one to combine data with taxonomic information as, for example, in the above condition  ?D:Drive[agent -> ?E1:Entity, follows -> ?S:Entity], which states that there should be a drive D whose agent is entity E1 and that drive follows  a driving situation S.  (Incidentally, incorporating taxonomic information into frames typically leads to faster rules.) There is no need to repeat the same taxonomic information more than once  so, for example, the second time ?S and ?E1 occur in the rule without that information (i.e., just as ?S and not as ?S:DrivingSituation).  That information does not need to come with the first occurrence of a variable either (cf. ?E2).

As we write each rule, we test it by creating a mock-up situation and ensuring that it gives us the result we want. Each rule should be tested in isolation, and also in a way where it interacts with other rules in the knowledge base. We will give a test case for each rule as we introduce it. For the above rule, consider the situation:

// Test 1: testing illegal_drive

P1 : DrivingEntity.

P2 : DrivingEntity.

D1 : Drive [agent -> P1, follows -> S1].

S1 : DrivingSituation [participant -> {P1,P2}, rightOfWay -> P2].

In other words, two driving entities, P1 and P2, are involved in a driving situation S1 and P2 has the right of way. Nevertheless, P1 makes the first move (because drive D1, caused by agent P1, immediately follows S1). To test our rule, we pose a query at the Ergo command prompt: would D1 be an illegal drive?

ergo> D1:IllegalDrive.

Yes

The answer of ``Yes’’ confirms the intended behavior of this rule and gives us some assurance that we are on the right way.

We represent the third sentence from the handbook by stating that having the right of way is a necessary but not sufficient condition for a safe and legal driving action.

Functional sentence SafeDrive:  A safe drive is a drive. If a driving entity E is involved in a driving situation S and what follows S is a safe drive then E must have had the right-of-way in the driving situation S.

SafeDrive :: Drive.

@!{right_of_way_safe_drive} 

?S [ rightOfWay -> ?E] :-

?S : DrivingSituation[participant -> ?E],

?E : DrivingEntity,

? : SafeDrive[follows -> ?S, agent -> ?E].

Recall that ? in the last line stands for a new, anonymous variable.   This is typically used to avoid having to invent distinct names for variables that occur just once in a rule.

To test this rule, we introduce the following test case:

// Test 2: testing right_of_way_safe_drive

S2:DrivingSituation [participant -> P3].

P3: DrivingEntity.

D2: SafeDrive[ follows -> S2, agent -> P3].

This mock-up scenario says that P3 is a driving entity that is participant in a driving situation S2, and the safe drive D2 follows S2 immediately. Who has the right of way here?

ergo> S2[rightOfWay -> ?x].

?x = P3

1 solution(s) in 0.0160 seconds

Yes

Yes, P3! This confirms that the above right_of_way_safe_drive rule appears to be functioning as expected.

Let us now turn to the second paragraph in the same section of Driver's Handbook:

This paragraph is a source of many useful terms for the driving domain. Sentence 4 introduces the notion that the right of way is not limited just to yielding to others when needed, but it also means obeying the law, as suggested in Sentence 5. Sentence 6 is a general statement highlighting the importance of following the right-of-way law. From this paragraph, we will only represent the terms introduced in sentence 4, and represent Sentence 5 as a rule violates_ROW below. We begin by defining the terms introduced in this paragraph.

We define the class Person and make Pedestrian into its subclass. The classes Bicyclist, Motorist, and MotorCyclist are defined as subclasses of DrivingEntity and Person.

Person :: Entity. 

Pedestrian :: Person.

Bicyclist :: {DrivingEntity, Person}. 

Motorist :: {DrivingEntity, Person}.

MotorCyclist :: {DrivingEntity, Person}.

Car :: DrivingEntity.

The classes Crosswalk and StreetCorner are introduced as subclasses of SpatialEntity. We also define TrafficSign as a subclass of SpatialEntity, and StopSign and TrafficSign as subclasses of TrafficSign.

{CrossWalk, StreetCorner, TrafficSign} :: SpatialEntity.

{StopSign, TrafficSignal} :: TrafficSign.

We define FailToStopAtTrafficSign, Speeding, MakingIllegalTurn and MakingUnsafeLaneChange as subclasses of IllegalDrive

FailToStopAtTrafficSign :: IllegalDrive. 

Speeding :: IllegalDrive.

MakingIlliegalTurn :: IllegalDrive.

UnsafeLaneChange :: IllegalDrive.

Next comes the property violatesRightOfWay with domain Drive and range Entity. For this we again use the [|...|] notation.

Drive :: Action[|violatesRightOfWay => Entity|].

To represent the intent of Sentence 5, we say that any IllegalDrive violates the right of way of others that are also on the road.   

Functional sentence for violation of the right of way:   If entities P and Q are involved in a driving situation S and P makes an illegal drive I in that situation, then the illegal drive I violates the right of way of Q.

@!{violates_ROW}

?I[violatesRightOfWay -> ?Q] :-

?S : DrivingSituation[participant -> {?P,?Q}:Entity],

?I : IllegalDrive[agent -> ?P, follows -> ?S],

?P != ?Q.

To test this rule, we introduce the following situation:

// Test 3: testing violates_ROW

S3 : DrivingSituation[participant -> {P4,P5}:Person].

I1 : FailToStopAtTrafficSign[agent -> P4, follows -> S3].

and verify the rule by posing the following query:

ergo> I1[violatesRightOfWay -> ?x].

?x = P5

1 solution(s) in 0.0310 seconds

Yes

Indeed, the illegal drive I1 violates the right of way of P5, so the test confirms the correct functioning of the rule. It is not necessary to represent Sentence 6 as it does not provide operational or actionable information for a driver.

Next, we consider the 6th paragraph on the Right-of-Way page:

  4. Respect the right-of-way of pedestrians. Always stop for any pedestrian crossing at corners or other crosswalks, even if the crosswalk is in the middle of the block and at corners with or without traffic lights, whether or not the crosswalks are marked by painted lines.

Here is a representation of this sentence:

Functional sentence for right-of-way of pedestrians:  If a pedestrian P is involved in a driving situation at location C, which is either a crosswalk or a street corner, then P has the right of way in this driving situation.

@!{right_of_way_pedestrian}

?S[rightOfWay -> ?P] :-

(?C:CrossWalk \or ?C:StreetCorner),

?S:DrivingSituation[location -> ?C, participant -> ?P:Pedestrian].

To test this rule, we introduce the following situation:

// Test 4: testing right_of_way_pedestrian

C1 : CrossWalk.

P6 : Pedestrian.

S4 : DrivingSituation[location -> C1, participant -> P6].

and then pose the following query:

ergo> S4 [rightOfWay -> ?x].

?x = P6

1 solution(s) in 0.0000 seconds

Yes

Indeed, P6 is involved in a driving situation at a crosswalk and so has the right of way. Obtaining a correct answer to this query confirms the correctness of the rule right_of_way_pedestrian.

Additional test questions

Now that we have tested each rule separately, let us consider new test questions that exercise more than one rule at a time. This will help ascertain that the rules are chained properly and will provide an assurance of the overall question answering ability of our knowledge base, as well as the ability to handle new questions that are not given ahead of time.

Consider the question: A car and pedestrian are stopped at a corner. If the car proceeds, whose right of way will it violate? To answer this question, the system first needs to determine that the pedestrian has the right of way, and by noticing that the car violates the right of way conclude that it is an illegal drive. From this it can then conclude that this drive must violate the right of way of the pedestrian. To test this, we construct the following situation:

// Test 5: A car and a pedestrian are stopped at a corner ...

P7 : Car.

P8 : Pedestrian.

C3 : StreetCorner.

S6 : DrivingSituation[location -> C3, participant -> {P7,P8}].

D3 : Drive[agent -> P7, follows -> S6].

The first four statements say that there is a driving situation at a street corner that involves a car and a pedestrian. The last statement says that the car proceeds to drive next (which we expect to be illegal). As an aside, now that we have seen many examples of the frame syntax, it is a good time to remember that the first four statements can actually be condensed into one:

S6:DrivingSituation[location->C3:StreetCorner, participant->{P7:Car,P8:Pedestrian}].

Let us now pose this query: Does the car's drive action violate somebody's right of way and who that somebody is?

ergo> D3[violatesRightOfWay -> ?x].

?x = P8

1 solution(s) in 0.0160 seconds

Yes

As we see, the query returns the expected answer: the car's action violates the right-of-way of the pedestrian.  This is a non-trivial inference and one might want to have it explained. Fortunately ErgoAI Studio provides a great way to have it done. The following picture shows a partially expanded explanation tree for the above query answer.

Explanation in Ergo Studio

The picture shows that the answer was derived via an inference process that was 3-levels deep. The first two levels are mostly expanded in the picture and the third is not.  One can expand or hide a particular inference by clicking the blue handle shown on the right on some of the lines in the picture.

On the wings of this success, let us now consider yet another question: If I do not stop at a traffic sign, do I violate somebody’s right of way? For this, we create the following test case:

// Test 6: If I do not stop at a traffic sign ...

{P9,P10} : Entity.

S7 : DrivingSituation[participant -> {P9,P10}].

F1:FailToStopAtTrafficSign[agent -> P9, follows -> S7].

or, more succinctly,

S7 : DrivingSituation[participant ->  {P9, P10}:Entity]. 

F1:FailToStopAtTrafficSign[agent -> P9, follows -> S7].

To answer this question, Ergo must realize that FailToStopAtTrafficSign is a special case of IllegalDrive, and so the rule that defines the property violatesRightOfWay applies to F1. With this rule, Ergo concludes that the action F1 must violate the right of way of whoever might be at the same location. Indeed, if we pose the following query, we get the expected result:

ergo> F1[violatesRightOfWay -> ?x].

?x = P10

1 solution(s) in 0.0000 seconds

Yes

In other words, if P9 fails to stop at a traffic sign, that entity violates the right-of-way of P10.

So far, we have been constructing test cases by adding facts to the KB and then issuing queries. However, this has a problem: test case facts tend to accumulate and one needs to make an effort to keep these facts from interfering with other tests. For example, if in the last example we make a mistake and reuse P7 and P8 instead of inventing the new names P9 and P10, then the results to the last query might turn out differently because of the interference between the test cases. There can even be more subtle interactions among the facts in different tests, especially if they were conceived and added days or weeks apart. This problem can be overcome via a pair of more advanced features of Ergo: hypothetical queries and Skolem constants. The former enables one to create a test case, temporarily add its facts to the KB, and then ask a query in the new state of the KB. After the query is answered, the state of the KB is rolled back to what it was before the temporary change. A Skolem constant is a special symbol, which gets replaced by the compiler with a completely new constant that has not been seen before. Thus, hypothetical queries let us do testing without accumulating "junk" in the knowledge base, while Skolem constants give us a convenient way of avoiding name clashes between constants in a test case and in the main KB. We can now redo Test 6 as follows:

ergo>   <>(

         // Test 7: hypothetical variant of Test 6

         tinsert{\#1:DrivingSituation[participant->{\#2, \#3}:Entity],

                 \#4:FailToStopAtTrafficSign[agent->\#2, follows-> \#1]}, 

                 \#4[violatesRightOfWay->?x]

        ).

The answer to this query is ?x = \#3.

Let us unpack the above. Here <>(...) signifies that the expression ... is a query to be executed hypothetically, i.e., any changes that it might do would be rolled back. The expression tinsert{...} is a transactional insert. This means that inside is a set of facts that is to be inserted transactionally, i.e., in case of a failure the partially inserted facts should be uninserted. The next line is a query that is isomorphic to the earlier query F1[violatesRightOfWay -> ?x]. Hypothetical queries are allowed to use only transactional insert and delete operators (Ergo also has non-transactional insert/delete, but this is out of scope here).

The strange symbols \#1, \#2, etc., are the aforementioned Skolem constants. When the compiler sees, say \#1, it invents a completely new symbol and the same happens with \#2. If it sees the same Skolem symbol, say \#2, twice within the same query then it is replaced with the same new constant. So, the repeated occurrences of \#1, \#2, etc., refer to the same new constant (but \#1 and \#2, of course, are different new constants). However, in different hypothetical queries, the occurrences of \#1 (\#2, \#3) are replaced with different constants. Therefore, Skolem constants can be reused in different queries without the fear of clashes. We can now see that our hypothetical query is isomorphic to Test 6 except that the reusable Skolem constant \#1 is used in place of S7; the Skolem constants \#2 and \#3 instead of P9 and P10, and \#4 instead of F1. This is, in fact, a more robust way to perform tests, but the use of the Skolems and hypothetical queries is far wider than just testing. In fact, hypothetical questions are quite common in the real world.

Elements of advanced testing

We have tested a number of rules separately and together, but are there latent errors? One source of such errors is violation of the typing constraints. Indeed, we have specified types for the various classes (via the statements of the form Class[| prop => Type |]), but we have not taken advantage of this information so far. Now, let us ask the question: Are any of the types violated by the existing test data? To test this hypothesis, we could ask the following query:

ergo>   ?C[|?P => ?T|], ?O:?C[?P => ?T], ?O[?P -> ?V], \naf ?V:?T.

That is, is there a class ?C with a specified property ?P of some type ?T and an object ?O in ?C that inherited that type information such that some ?P's value ?V with respect to the object ?O violates the type (i.e., \naf ?V:?T)? It turns out we do have a problem, as the query reports these violations:

?C = DrivingSituation

?P = rightOfWay

?T = DrivingEntity

?O = S4

?V = P6 

?C = DrivingSituation

?P = rightOfWay

?T = DrivingEntity

?O = S6

?V = P8

Aha! P6 and P8 are supposed to be in the class DrivingEntity because Ergo must have inferred S4[rightOfWay->P6] and S6[rightOfWay->P8], and our KB says that DrivingSituation[|rightOfWay=>DrivingEntity|]. However, our KB says that these two objects are members of the class Pedestrian and one cannot conclude from this that they are driving entities.

Now, we can spot a number of problems in our little knowledge base. First, apparently, we meant to state Pedestrian::DrivingEntity. Or, maybe, we meant to have TrafficEntity as a common superclass of Pedestrian and DrivingEntity (and then the type of rightOfWay would be TrafficEntity instead of DrivingEntity). Second, looking at the uses of the properties participant and rightOfWay we zero in on the rule illegal_drive.  Ergo, in fact, has a way to query rules and find those where such properties are used. Now, we see that ?E2 in that rule must be at least a DrivingEntity (as a value of rightOfWay). But two lines above that, we see a condition {?E1,?E2} : Entity, which is too loose - we probably meant that at least ?E2 must be a driving entity. And, indeed, we probably do want ?E1 and ?E2 to be driving entities in that rule, for too many things can be members of Entity (e.g., traffic signs). For instance, we don't want to deduce a drive as illegal if we encounter a traffic sign and make a move right after that: a traffic sign is not a driving entity and it cannot commit any acts by itself. However, after tightening that rule (to require ?E2:DrivingEntity by uncommenting line 4 of the rule), Test 4_2 (which is constructed as a combination of Tests 1 and 4 -- see right_of_way_tests.ergo) will fail and one way to fix it is to uncomment the line Pedestrian :: DrivingEntity in right_of_way_KB.ergo.

We should note that Ergo does provide a builtin library for type checking, which lived in module \typecheck, so one does not have to figure out all the different cases of type errors. For instance,

ergo>   Type[check(?[|?->?|],?Result)]@\typecheck.

will return

?Result =  [ ( ${S4[rightOfWay->P6]@main},  ${S4[rightOfWay=>DrivingEntity]@main} ),

            ( ${S6[rightOfWay->P8]@main},  ${S6[rightOfWay=>DrivingEntity]@main} ) ]

which is equivalent to the result returned by our type query above. For instance, the first line above states that S4[rightOfWay->P6]@main does not comply with the declared type S4[rightOfWay=>DrivingEntity]@main, and similarly for the second line. See  ErgoAI Reasoner User's Manual (the section on Type Checking) for the details of this library.

Other omissions are also possible. For instance, is there a data object such that some of its properties are given, but no type for that property was specified in that object's class? We can check for such undeclared properties by posing the following query:

ergo>  ?Obj[?Property->?], \naf exists(?Class)^(?Obj:?Class, ?Class[|?Prop=>?|]).

This query gives us the following answers:

?O = S4

?Property = location

?O = S6

?Property = location

Aha! The location property was used in the sample data (e.g., in  S4:DrivingSituation[location -> C1, participant -> P6]), but we never included that property in the ontology. Where should it be included? Probably in a statement like  DrivingSituation[|location=>SpatialEntity|].  For completeness, we should mention that the Ergo \typecheck library has a builtin query to test this kind of a situation also:

ergo>   Type[check(?[?->?],?Result)]@\typecheck.

which yields the following answer:

?Result =  [ ( ${S4[rightOfWay->P6]@main},  ${S4[rightOfWay=>DrivingEntity]@main} ), 

             ( ${S6[rightOfWay->P8]@main},  ${S6[rightOfWay=>DrivingEntity]@main} ),

             ${S4[location->C1]@main},

             ${S6[location->C3]@main} ]

The first two lines are the same as in the result for our query Type[check(?[|?->?|],?Result)]@\typecheck a few paragraphs earlier -- they show examples of type violations. The last two facts in the above result are examples of facts in our KB, which use undeclared properties.

We conclude this section by mentioning one other relevant feature of Ergo: alerts. So far, if type constraints (or any other kind of constraints) get violated, one does not get notified and the violations will go unnoticed until the user explicitly runs a query to check the constraint (like we did above). In Ergo, however, one can designate a query as a constraint or an alert (again, check  ErgoAI Reasoner User's Manual for details) and the constraint will be checked automatically. For instance, at the bottom of right_of_way_KB.ergo, our earlier type-checking query is specified as an alert. If one uncomments the last 5 lines of that file, and loads our example:

ergo>   [right_of_way_KB, +right_of_way_tests].  // load the example and add tests

then the following alert will appear after loading:

*** Truth alerts raised after transaction add{ +(right_of_way_tests) }:

            type_alert(S4,DrivingSituation,rightOfWay,P6,DrivingEntity)

            type_alert(S4,DrivingSituation,rightOfWay,P6,DrivingEntity)

            type_alert(S6,DrivingSituation,rightOfWay,P8,DrivingEntity)

            type_alert(S6,DrivingSituation,rightOfWay,P8,DrivingEntity)

These alerts were activated on line 8 in file right_of_way_KB.ergo

Notes on knowledge base organization

The files associated with this tutorial are split into two: the file right_of_way_KB.ergo file, which contains the knowledge base itself (i.e., the taxonomy of classes, the rules) and right_of_way_tests.ergo - the file that contains the tests. Clearly, tests should not be mixed up with the KB, so this division is understandable. But how does one load the KB and run the tests? There are several ways. The simplest is to load the KB and then to add the tests:

ergo>  [right_of_way_KB]. // load the KB itself; equivalently: load{right_of_way_KB}.

ergo>   [+right_of_way_tests]. // add the tests; equivalently: add{right_of_way_tests}.

The above can also be accomplished via a single command as follows:

ergo>  [right_of_way_KB, +right_of_way_tests]. // load the entire example in one command

In Ergo, knowledge bases are divided in modules, where each module can be viewed as a separate encapsulated component. Modules can interact with each other via querying, but otherwise they are independent and, most importantly, do not interfere with each other in unpredictable ways. In the above example, the file right_of_way_KB.ergo is loaded into the default module main (the extension can be omitted in the loading and adding commands). Only one file can be loaded into a module. If more need to be, the other files must be added, which is what we do in the second statement. (If two files are loaded into the same module, the second load wipes out the existing contents of the module and loads the contents of the second file into that module. This is a fast and convenient way to reuse an existing module by replacing its contents. Adding a file to a module does not wipe out the previous contents but, as the name suggests, augments the old contents with additional rules and facts.) Generally, if several files must live in the same module, the file containing the most rules should be loaded and the others added. This is because loaded rules execute slightly faster.

The above example uses only one module, but complex KBs are normally divided into several modules. The default module main is usually used for the main application - the one that queries the various sub-KBs (the independent KB components) and executes that application's commands. These sub-KBs are best kept in separate modules so they will stay out of the way of each other and of the main application. This prevents the different KBs and the main application from unintended interference. For instance, our example KB could be loaded into a module called CDHB (California Driver's Handbook) and the test data with the queries into the default module main:

ergo>   [right_of_way_KB_modular >> CDHB]. // load the KB into the module CDHB;

ergo>   [right_of_way_tests_modular].      // load the tests into the main module

Note that since these files are intended to be in different modules, we can load them both into these modules without wiping out the contents. (As noted, loaded rules are slightly faster, our tests contain data only (and queries), so adding right_of_way_tests_modular.ergo would have worked just as well.)

For this latter example, we are using the slightly modified files right_of_way_KB_modular.ergo and right_of_way_tests_modular.ergo for the following reason. First, since the KB and the tests are loaded into different modules, the KB has no data to work on and needs to be told how to get it. The simplest way is to import the module main that contains the data into the KB's module CDHB. This is done with the help of the importmodule directive included in right_of_way_KB_modular.ergo on line 8. Second, the queries in the file right_of_way_tests_modular.ergo refer to properties, like violatesRightOfWay and to the class hierarchy, defined in module CDHB (in which the KB is loaded). Since this is a different module from the one where the queries reside (they are in main, the module of their host file), we need to add @CDHB to the appropriate queries (e.g., F1[violatesRightOfWay -> ?x]@CDHB). These are the only essential differences between right_of_way_tests_modular.ergo and right_of_way_tests.ergo. The other difference is that the former file also contains the loading command [right_of_way_KB_modular>>CDHB] so that the entire example could be run by simply typing this at the Ergo command prompt:

ergo>  [right_of_way_tests_modular]. // load tests in module main (and the KB in module CDHB)

In general, it is a good practice to arrange an Ergo application so that loading one or two files would execute the appropriate loading commands for all the other files. The above examples illustrates some of the useful idioms for achieving that.