Perplexity Ontology

In order to implement the predicates output by the MRS, I had decide what the underlying data structure would be to encode facts in Prolog like:

The thing that defines how you write down those facts (this “underlying structure”) is called an “Ontology Language” or “Knowledge Representation Language”. One of the more famous “Ontology Languages” used today is called “OWL” and it is used by the “Semantic Web” effort.

Then, I had to use this knowledge representation to decide on a structure for the facts, relationships and logic rules for Perplexity that define what can be said and done. This is called an “ontology”.

Both the knowledge representation and ontology in Perplexity are very simple. Dealing in such a conceptual space can lead you down a path of over-engineering very quickly. To combat this, I’ve tried to introduce things only when they were actually used by an actual ERG predicate not because “they made sense”.

Note that ontologies and knowledge representation are a huge world of research and examples. I don’t claim to have done anything all that novel here. The point of Perplexity wasn’t to explore this area per se, it just needed to be done. I will say, though, that I designed this ontology by starting with the notion of a triple store and designing the actual ontology by going through MRS output, phrase by phrase. So, in some sense, this ontology has been designed specifically for the ERG, or at least to work well with it.

When I started Perplexity it was really hard to find examples of how a system like this would get built. My hope is that this can serve as an example to others to help them get up to speed way more quickly.

Perplexity Knowledge Representation: Atoms and Triples

The most basic unit of information in Prolog is the “atom” which is simply a symbol like foo. In Perplexity, every thing that exists in the world (people, caves, diamonds) and even abstract concepts (the color property of rock1) are represented by a Prolog atom. Some examples of atoms that are actually in the prototype:

idThing.                % Represents the concept of a "thing" which is the root of everything
idConcept.              % Represents the concept of "concept"
idLockedStateLocked.    % Represents the concept of something being "locked"
idOpening.              % Represents the concept of an "opening" of some kind
idLexi.                 % Represents the A.I. robot that you talk to
idKey1_prop_idColor.    % Represents the color property of key1

I try to prefix these with id to reinforce the fact that they aren’t names. Names are provided by adding even more information. Every single thing you can say about a particular atom is represented by a “triple” which is simply three values that express a relationship: subjectAtom, relationshipAtom, objectAtom. Even the relationships are a type of atom.

Here are some example relationship atoms from Perplexity:

% These two are used to build the hierarchy of kinds and create instances of things
specializes.
instanceOf.
% These two are used to create properties and set their values
propertyOf.
hasState.

Relationships are manipulated using three predicates:

% Adds the relationship (and its atoms) into the world. All args must be atoms
store_assert(Subject, Rel, Object, EventSpace).      

% Removes the relationship from the world. All args must be atoms
store_retractall(Subject, Rel, Object, EventSpace). 

% Does a normal Prolog query against the relationship store
store(Subject, Rel, Object, EventSpace).    

This allows the state to be cleanly stored, retrieved, and deleted in one place. Atoms don’t need to be created, they are created as they are used in a relationship.

There is an extra argument EventSpace in the above predicates. EventSpace allows specifying what “world” this relationship is true in. Language often talks about things that aren’t true now. Take “Put the book on the table”: the book is not currently on the table, the speaker wants it to be. I needed a way to store this without it getting confused with what is actually in the real or “default” world. Things that actually exist (i.e. that are in the “default” world) use EventSpace = default. Relationships that someone might want to be true like “having a book on the table” (that isn’t now) would be in an event space of some (effectively) random atom like g20. There’s a whole section about events that talks through this.

Note that you’ll also see these helpers used in all the examples below because I thought it made things clearer:

% just calls store_assert(Subject, Rel, Object)
:- assertTerm(rel(Subject, Rel, Object)).  

% just calls store_assert(Property, hasValue, Value)
:- assertState(Property, Value).

% A synonym used to query the "default" world        
rel(Subject, Rel, Object).

Perplexity Ontology

There’s not a lot to the Perplexity Ontology. It was surprising how rich the world could be with only a few concepts. The entire demo is built from three chunks of ontology:

That’s it!

Core Ontology

Perplexity has a very simple core set of predicates that use a few atoms and relationships that everything else builds on: Types, Instances and Properties. There are other relationships in the system but these are the ones the system bootstraps with.

Types and Instances

This is how you say what a thing “is a” (its “type”) and declare that things exist (are “instances”) in the world.

A type is a “class” of things. It is a concept that does not actually exist in the world yet, i.e. it is not an “instance”. It is very much like class in an OO language like C++:

An instance is a specific thing which exists physically (e.g. an actual rock) or conceptually (e.g. a particular name)

Perplexity has a very limited set of enforced definitions here to keep things simple. This means all sorts of bugs could be introduced and not get noticed: loops in the type hierarchy, types that specialize instances, etc. This is the literally all the Prolog code that currently defines types and instances:

% X specializes Y if there is a direct specialization  
specializes(Type, BaseType) :-  
  rel(Type, specializes, BaseType).
    
% True if object has a sequence of specializations that lead to BaseObject  
specializes(Type, BaseType) :-  
  rel(Type, specializes, IntermediateType),  
  specializes(IntermediateType, BaseType).  
  
% the straightforward interpretation  
instanceOf(Instance, Type) :-  
  rel(Instance, instanceOf, Type).  

% an item is an instance of a Type if it is an instance of something that  
% specializes type  
instanceOf(Instance, BaseType) :-  
  rel(Instance, instanceOf, InstanceType),  
  specializes(InstanceType, BaseType).  
  
% If something is an instance of something else, it can't also specialize something  
% Useful if we are determining if X is an actual instance of something  
isInstance(X) :-  
  count(Count, instanceOf(X, _)),  
  >(Count, 0).

So to create a thing which is a rock using types and instances, you would say:

:- assertTerm(rel(idPlace, specializes, idThing)).
:- assertTerm(rel(idPhysicalObject, specializes, idPlace)).  
:- assertTerm(rel(idRock, specializes, idPhysicalObject)).  
:- assertTerm(rel(idRock1, instanceOf, idRock)).

Properties

Properties are things like X for which you would say “An object exhibits X” or “An object has the characteristic X”. Colors, textures, visibility are all properties.

The state (i.e. value) of a property can often be described by “is” or “can be”, as in:

Adding a property to something makes it “light up” in a couple of very general ways right off the bat:

% defining a property "color" will make phrases like "the rock has a color" work
% since possessedBy() looks for any property
possessedBy(WhatID, WhoID) :-  
  rel(WhatID, propertyOf, WhoID).

% Anything that looks for adjectives will treat all of the
% properties of an object as adjectives because of this core predicate
adjectiveOfArg(Property, X, PropertyValue) :-
  ...

Here is an example of defining a color property that is blue for a specific rock:

% define the rock type
:- assertTerm(rel(idPlace, specializes, idThing)).
:- assertTerm(rel(idPhysicalObject, specializes, idPlace)).  
:- assertTerm(rel(idRock, specializes, idPhysicalObject)).  

% An instance of a rock...
:- assertTerm(rel(idRock1, instanceOf, idRock)).

% Define the color type
:- assertTerm(rel(idConcept, specializes, idThing)).
:- assertTerm(rel(idAdjective, specializes, idConcept)).
:- assertTerm(rel(idColor, specializes, idAdjective)).

% Set the rock to have the color blue
:- assertTerm(rel(idRock1_prop_idColor, instanceOf, idColor)).  
:- assertTerm(rel(idRock1_prop_idColor, propertyOf, idRock1)).  
:- assertTerm(rel(idRock1_prop_idColor, hasState, 'blue')).  

% Note that setting state has a helper property just for readability. 
% So the above could also be:
:- assertState(idRock1_prop_idColor, 'blue').

Unlike everything else that is an atom, the state of properties like ‘blue’ above is a string. That deserves more discussion.

### Property Values It gets really easy to have an explosion of relationships when defining an ontology. Should we, for example, define every color that exists as an instance of color? Maybe. Probably. But for simplicity I decided to just shortcut the process and assume that every value of a property in the system is a valid value for that type of thing. This worked well enough and saved a lot of pain.

Said another way: the enumeration of valid values for a property is collected by looking at all the values that things with that property have.

So you’ll see that, if you type “what colors are there?” in Perplexity it responds with “blue, green and red”. Clearly not a full enumeration of colors. It only “knows” colors that things in the Perplexity world actually have right now. The same is true of every property/adjective.

Core Types

There are some types that are used by a lot of the Perplexity system and I’ll walk through the big ones in this section.

Names and Proper Names

Just using the above example and running the prototype wouldn’t get you very far because nothing has a name yet. Nothing you say would be understood.

Names are just just a property of things. Here are a few examples with the names included:

% A "name" itself is a thing and it has a name which is: "name"
:- assertTerm(rel(idName, specializes, idConcept)).  
:- assertTerm(rel(idName_prop_idName, instanceOf, idName)).  
:- assertTerm(rel(idName_prop_idName, propertyOf, idName)).  
:- assertState(idName_prop_idName, 'name').

% places are called "place"
:- assertTerm(rel(idPlace, specializes, idThing)).
:- assertTerm(rel(idPlace_prop_idName, instanceOf, idName)).  
:- assertTerm(rel(idPlace_prop_idName, propertyOf, idPlace)).  
:- assertState(idPlace_prop_idName, 'place').

% Proper name is a kind of name
:- assertTerm(rel(idProperName, specializes, idName)).

% Lexi's proper name is "Lexi"
:- assertTerm(rel(idLexi_prop_idProperName, instanceOf, idProperName)).  
:- assertTerm(rel(idLexi_prop_idProperName, propertyOf, idLexi)).  
:- assertState(idLexi_prop_idProperName, 'Lexi').

As you might imagine, there are a bunch of predicates to help with names, so I won’t list them all here. Rest assured that all they use are the name types described above.

Nouns: idName and idProperName

Maybe it is obvious by now, but defining a new noun in the system is trivial: just give an atom a name.

Here is an example of a common and a proper noun:

% Here's a new common noun "person" (common nouns are types)
:- assertTerm(rel(idPerson, specializes, idPhysicalObject)).  
:- assertTerm(rel(idPerson_prop_idName, instanceOf, idName)).  
:- assertTerm(rel(idPerson_prop_idName, propertyOf, idPerson)).  
:- assertState(idPerson_prop_idName, 'person').

% Here's an actual rock in the world that doesn't have a special name 
% (its type name will be used: "rock")
:- assertTerm(rel(idRock1, instanceOf, idRock)).

% Here's a proper noun "Lexi" that is an instance of person (proper nouns are instances)
:- assertTerm(rel(idLexi, instanceOf, idPerson)).
:- assertTerm(rel(idLexi_prop_idProperName, instanceOf, idProperName)).  
:- assertTerm(rel(idLexi_prop_idProperName, propertyOf, idLexi)).

Adjectives: idAdjective

Adjectives in Perplexity are created simply by creating a property of type idAdjective and using it. It’s a very low overhead thing to do.

Here I define a new adjective “height” and set the back opening of the first cave to be “tall”:

:- assertTerm(rel(idHeight, specializes idAdjective)).
:- assertTerm(rel(idHeight_prop_idName, instanceOf, idName)).  
:- assertTerm(rel(idHeight_prop_idName, propertyOf, idHeight)).  
:- assertState(idHeight_prop_idName, 'height').

:- assertTerm(rel(idEntrancecaveBackOpening_prop_idHeight, instanceOf, idHeight)).  
:- assertTerm(rel(idEntrancecaveBackOpening_prop_idHeight, propertyOf, idEntrancecaveBackOpening)).  
:- assertState(idEntrancecaveBackOpening_prop_idHeight, 'tall').

Viola! I’ve just introduced height to the world!

Other Parts of Speech

Nouns and adjectives have a uniform handling in the core system and this makes it easy to define them just using relationships as described above. Other parts of speech like verbs, adverbs, prepositions, etc require custom logic and thus building Prolog predicates. This isn’t really a part of the ontology, but it does have a whole section that describes how it is done in Perplexity.

Synonyms

What if you define a “texture” property but also want “feel” to work in addition to “texture”? The Perplexity synonym system is a topic on its own, but, from an ontology perspective, the trick is to figure out what predicate is used by the ERG for the adjective and then use the same synonym system to describe the synonym words.

This is easier to show with an example. If texture was already defined as a property somewhere, all you’d have to do to give synonyms is this:

vocabulary("texture", 'd_texture_n__x', [], [word, instance], noun, 
  ["consistency", "quality", "surface", "coarseness", "feel", "roughness"]).

That says that the way texture is used here converts to d_texture_n__x in the ERG, and, when used like that, possible synonyms are “consistency”, “quality”, “surface”, “coarseness”, “feel”, “roughness”. It needs this level of information because words can be used in different ways. Synonyms should only kick in if they are being used as expected. Pinning the word texture to a specific ERG predicate gives a lot of information that specifies what we mean by “texture” in this synonym declaration. “Feel” can also be a verb, and we don’t want “texture” to be a synonym there! Read the section on synonyms for more information.

Property values also need synonyms but these are simpler. They just need the “type” of thing, a value it might have, and a list of synonyms:

propertySynonym(idColor, "blue", ["blueish", "bluish", "azure", "beryl", "cerulean", "cobalt", "indigo", "navy", "royal", "sapphire", "teal", "turquoise", "ultramarine"]).
propertySynonym(idTexture, "rough", ["bumpy", "coarse", "unfinished"]).
propertySynonym(idTexture, "smooth", ["uniform"]).

Perplexity Ontology Language

Writing down the facts in the ontology got repetitive, so I built a very simple domain language that I find easier to read and write. I’m going to use it to describe the Physics engine for this reason. So, here’s a quick description:

Core Ontology Language

Each statement in the language starts with an atom like idBook and then sets a series of one or more relationships it has within {}:

idBook { specializes idPhysicalObject }
idRock2 { instanceOf idRock }
% idEntrancecaveBackOpening has two relationships in this example
idEntrancecaveBackOpening { instanceOf idOpening, idPartOfRel idEntrancecaveBack }

One type of relationship that would have taken a few lines to create is a property. These are created like this:

% idBook1Page3 is an instance of idPage and 
% has a property of type "idCardinality" with a value of '3'
idBook1Page3 { instanceOf idPage, .idCardinality = '3' }

% ...converts to the following
% (the property itself is given a made-up name "idBook1Page3_prop_idCardinality" 
% by the system
:- assertTerm(rel(idBook1Page3, instanceOf, idPage)).
:- assertTerm(rel(idBook1Page3_prop_idCardinality, instanceOf, idCardinality)).  
:- assertTerm(rel(idBook1Page3_prop_idCardinality, propertyOf, idBook1Page3)).  
:- assertState(idBook1Page3_prop_idCardinality, '3').

So now you’ve seen everything about how to create the core ontology of Perplexity in this language: instances, types and properties! Like I said, simple.

Macros: Noun, Adjective, Face

The language also implements “macros” which just means “things that take arguments and get transformed into something else”. These always start with a capitol letter, parenthesis, and one or more arguments. I use three in the Physics examples: Noun(), Adjective(), and Face():

atom { Noun('name', 'synonym1', 'synonym2', ...) }

idColor { specializes idAdjective,  
    Noun('color', 'hue', 'chroma', 'chromaticity', 'coloration', 'coloring', 'tint') }

% ...converts to:
:- assertTerm(rel(idColor, specializes, idAdjective)).  
:- assertTerm(rel(idColor_prop_idName, instanceOf, idName)).  
:- assertTerm(rel(idColor_prop_idName, propertyOf, idColor)).  
:- assertState(idColor_prop_idName, 'color').  
vocabulary("color", 'd_color_n__x', [], [word, instance], noun, ["hue", "chroma", "chromaticity", "coloration", "coloring", "tint"]).  
vocabulary("color", 'd_color_n__xi', [], [word, instance, individual], noun, ["hue", "chroma", "chromaticity", "coloration", "coloring", "tint"]).

atom { Adjective(propertyType, 'value', 'synonym1', 'synonym2'...) }

idRock1 { instanceOf idRock,  
    Adjective(idTexture, 'smooth', 'uniform')}

% ...converts to:
:- assertTerm(rel(idRock1, instanceOf, idRock)).
:- assertTerm(rel(idRock1_prop_idTexture, instanceOf, idTexture)).  
:- assertTerm(rel(idRock1_prop_idTexture, propertyOf, idRock1)).  
:- assertState(idRock1_prop_idTexture, 'smooth').  
propertySynonym(idTexture, "smooth", ["uniform"]).

atom { Face(Bottom) }

idRock1 { instanceOf idRock, Face(Bottom) }

% ...converts to:
:- assertTerm(rel(idRock1Bottom, instanceOf, idFace)).  
:- assertTerm(rel(idRock1Bottom, idPartOfRel, idRock1)).  
% This is just historical.  What face something is needs to be
% indicated by the state of the side AND what the side "is"
% ...something I haven't cleaned up...
:- assertState(idRock1Bottom, idLocalBottom).  
:- assertTerm(rel(idRock1Bottom, instanceOf, idLocalBottom)).

Physics Ontolology

Perplexity lets you move around a world, grab things, stack things, etc. It implements a simple logical physics to do this and a special ontology to define “things in the world”. This was not simple to work out. There is an entire area of logic called Mereology (https://en.wikipedia.org/wiki/Mereology) that just focuses on the logic of “parts and wholes”, and that is just one part (pun intended) of what needs to be defined for implementing physics.

So, Perplexity has a very simple physics that gets a lot done with two high level ideas:

  1. Objects have bounding boxes which have sides
  2. The sides of objects can have relationships to the sides of other objects

All the “physics” in Perplexity: movement, composition, location, visibility, reachability, and positioning are all built with those two ideas.

The Shape of 3D Objects: Bounding Boxes

In order to have things inside each other, stack them, compose them together, and have them next to each other, they needed to have some kind of “3D” representation. Like in many video games I went with a “bounding box” model to simplify this. Thus, an object you can manipulate in Perplexity can have up to 7 sides:

// They have seven faces which are called 'local' since they don't change as the  
// object is moved.  They are used as states of an idFace  
idLocalTop { Noun('top', 'surface') }  
idLocalBottom { Noun('bottom', 'underside', 'base', 'underbelly', 'underneath') }  
idLocalLeft { Noun('left', 'port') }  
idLocalRight { Noun('right', 'starboard') }  
idLocalFront { Noun('front', 'forward') }  
idLocalBack { Noun('back', 'rear', 'backside', 'posterior', 'stern', 'aft') }  
idLocalInside { Noun('inside', 'interior') }

You’ll notice there is no “inside left, inside right, etc”: just inside. Spheres have 6 sides, there is no “sphere”. I found this to be enough to model containment and work in most scenarios.

Bottom Allows For Location

One extra important face is the bottom because this is how things are placed in the world: their bottom is touching something. You can’t put an object somewhere if it doesn’t have a bottom, so you’ll see lots of objects that just have a bottom so they can “be” somewhere:

idCrystal1 { instanceOf idCrystal, Face(Bottom) }  
idKey1 { instanceOf idKey, Face(Bottom),  
    Adjective(idColor, 'red', 'reddish', 'cardinal', 'coral', 'crimson', 'flaming', 'maroon', 'rose', 'burgundy', 'carmine', 'cerise', 'cherry', 'fuchsia', 'garnet', 'magenta', 'pink', 'ruby', 'russet', 'rust', 'salmon', 'sanguine', 'scarlet', 'vermilion', 'rosy', 'rubicund', 'ruddy', 'rufescent')}

Note that there is no notion of “exactly where” the bottom of something is. I.e. you can’t put the diamond “in the left corner of the cave” or “in the middle of the cave”. It is just “touching the inside of the cave”, period. (well, technically you could, but you’d have to build a much more complicated cave…).

Inside Allows For Containment

objectA can only be in objectB if objectB has an inside face. This includes obvious things with an “inside” like “a safe” but also less obvious things like the rooms and caves you move around in. These all use the same physics engine. So, for Lexi to be in a cave, she needs a bottom face, and the cave needs an inside face:

idPlage { instanceOf idCave, .idProperName = 'Plage', Face(Inside, Bottom, Front, Back) }
idLexi { instanceOf idPerson, .idProperName = 'Lexi', Face(Bottom, Left, Right)}

Maybe even less obvious is Lexi’s hands which also need an inside face so she can “have something in her hand”:

idLexiLeftHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'left')}  
idLexiRightHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'right')}

Composition: PartOf, Touching, Connected

There are only three types of relationship for “putting things together” and they vary on how they handle two aspects:

PartOf

Because PartOf isn’t a relationship between two sides (it is between something and a side), PartOf is only used when something is built into a side. Note that this is a directional relationship: what is on the left and right side of the relationship matters. Text is partOf a page, but the page is not partOf the text.

It is used for text on a page and openings in Perplexity:

// PartOf relationship is between a part and and the thing it is part of  
//   - Always between two objects  
//   - unpositioned, unseparable  
//   - Does not have a face connecting it, it has to have its own properties describing 
//    "how" it is part of it if more information is required
idPartOfRel { specializes idCompositeRel }

% Examples:
idBook1Page1Text { instanceOf idText, idPartOfRel idBook1Page1,  
    = 'dedicated to the DELPH-IN team' }
    
idPlageFrontOpening { instanceOf idOpening, idPartOfRel idPlageFront,  
    Adjective(idHeight, 'tall', 'big'),  
    .idDisplayName = 'a tall passage to the diamond cave',  
    .idOpenState = 'open'}

Touching

Touching is the only bidirectional composition relationship. Touching is how things:

Things that are touching can be moved around: the relationship is removed and added somewhere else.

For example, when you “get” or “pick up” an object in Perplexity, the idTouchingRel relationship between its bottom and what it is touching is removed. A new idTouchingRel is added between its bottom and the inside of Lexi’s hand.

// Touching relationships: two things that are next to each other  
//   - always between two idFaces  
//   - positioned, separate  
idTouchingRel { specializes idPositionalRel }

% Examples: 
% The book is on the the table
idBook1Bottom { idTouchingRel idTable1Top }  
% The table is in the cave
idTable1Bottom { idTouchingRel idEntrancecaveInside }  
% The diamond is in the cave
idDiamond1Bottom { idTouchingRel idEntrancecaveInside }

Connected

The idConnectedRel relationship allows you to build objects that move as a unit but that have parts that can be talked about separately. For example, Lexi has two hands that are idConnectedRel to her sides. That supports things like:

The fact that the safe is made out of a bunch of connected parts allows you to ask “is the X part of the safe?” and have it answer correctly.

Note that this is a directional relationship so what is on the left and right side of the relationship matters. Whatever is at the “top of the chain”: is considered the “whole” thing, and the rest are considered “parts” of it.

// idConnectedRel: 
//    - positioned, unseparable  
//    - Must be via a face  
//    - Should be one way where the minor thing is connected to the major thing  
idConnectedRel { specializes idPositionalRel }  
idConnectedRel { specializes idCompositeRel }

% Entire model for Lexi
idLexi { instanceOf idPerson, .idProperName = 'Lexi', Face(Bottom, Left, Right)}  
idLexiLeftHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'left')}  
idLexiRightHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'right')}
idLexiLeftHandBack { idConnectedRel idLexiLeft }  
idLexiRightHandBack { idConnectedRel idLexiRight }

% Entire model for the safe that locks
idSafe2 { instanceOf idSafe, Face(Inside, Left, Right, Back, Front, Bottom, Top) }  
idSafe2Top {  
    Adjective(idOpenState, 'closed'),  
    .idLockMechanism = idLock2 }  
idLock2 { instanceOf idLock, Face(Back, Front, Inside),  
    Adjective(idLockedState, 'locked') }  
idLock2_prop_idLockedState { .idRequiredTool = idKey1 }  
idLockFace2 { instanceOf idLockFace, Face(Back, Front) }  
idKeyhole2 { instanceOf idKeyhole, Face(Inside, Back) }

% Here's how the safe is all connected together
idLock2Back { idConnectedRel idSafe2Front }  
idLockFace2Back { idConnectedRel idLock2Front }  
idKeyhole2Back { idConnectedRel idLockFace2Front }

Scenario: Openings, Doors and Movement

Now we have enough background to build a door between two caves: You need two touching sides that have holes in them:

When you move from one place to another using “Go to Plage”, Perplexity:

Here’s how the passage from the first cave to Plage is modelled:

idEntrancecave { instanceOf idDiamondCave,  
    Face(Inside, Bottom, Back)}
idEntrancecaveBackOpening { instanceOf idOpening, idPartOfRel idEntrancecaveBack,  
    Adjective(idHeight, 'tall', 'big'),  
    .idDisplayName = 'a tall passage to a cave named `Plage`',  
    .idOpenState = 'open' }
    
idPlage { instanceOf idCave, .idProperName = 'Plage', Face(Inside, Bottom, Front, Back) }
idPlageFrontOpening { instanceOf idOpening, idPartOfRel idPlageFront,  
    Adjective(idHeight, 'tall', 'big'),  
    .idDisplayName = 'a tall passage to the diamond cave',  
    .idOpenState = 'open'}
    
% Here's where they touch
idEntrancecaveBack { idTouchingRel idPlageFront }

Scenario: Containment

thingA is “in” thingB if:

Scenario: Where Is X?

Describing location is tricky. I finally landed on telling you three potential things if you ask “Where is X?”:

"Where are you?"
Answer: inside the diamond cave and inside a world
[Because: Lexi is contained in both of those and not adjacent to or part of anything]

"Where is the book?"
Answer: below a safe, inside a diamond cave, inside a world, and on top of a table
[Because: the book is touching the sides of the table and the safe, and is
     contained in the other two]

Opening and Locking Ontology

The final piece of ontology is for locking and unlocking.

Whether something is “openable” is a function of whether it has a property of type idOpenState which can be set to “open” or “closed”. That’s it.

Locking works like this: A thing which has a property of type idOpenState can also have a property of type idLockMechanism. That property will point to a (potentially different) object. The system looks for a idLockedState property on that second object to see whether it is “locked” or “unlocked” to determine whether the first thing can be opened. This lets a safe have a top that can be locked by a lock somewhere else.

There is one final part to locking: what can unlock it? That is simply a property on the idLockedState property itself of type idRequiredTool. Here’s the full definition of the locking safe in the demo as an example:

idSafe2 { instanceOf idSafe, Face(Inside, Left, Right, Back, Front, Bottom, Top) } 
% the top of the safe `idSafe2Top` is what actually opens
idSafe2Top {  
    Adjective(idOpenState, 'closed'),  
    % but it points to `idLock2` as the `idLockMechanism`
    .idLockMechanism = idLock2 }  
idLock2 { instanceOf idLock, Face(Back, Front, Inside),
  % The lock starts out 'locked'  
    Adjective(idLockedState, 'locked') }  
% The `idLockedState` property of `idLock2` *also* has a property
% that says the required tool is `idKey1`
idLock2_prop_idLockedState { .idRequiredTool = idKey1 }  
idLockFace2 { instanceOf idLockFace, Face(Back, Front) }  
idKeyhole2 { instanceOf idKeyhole, Face(Inside, Back) }

Inherited Properties

There is one more trick to make this all work. When people try to get the safe unlocked they say all kinds of things like:

"Unlock the safe"
"Unlock the lock"
"Unlock the top of the safe"

How does all that work with the definition above? The predicates that implement locking use the compositional model described above to find out what things are a partOf and then “inherit” these properties to the larger thing.

So, even though the top of the safe is what actually “opens”, you can say “open the safe” because the “top” is partOf “the safe”.

Summary

Again, this is a very simple ontology but it was able to describe a surprisingly rich world. For reference, here is the entire definition of the world Perplexity uses, including the core and physics ontologies:

/* Bidirectional relationships */  
rel(idTouchingRel is bidi)  
  
// ************************  
// ************************  
// ************************  
// **** System objects  
// ************************  
// ************************  
// ************************  
idThing { Noun('thing', 'element', 'item', 'stuff', 'doodad', 'doohickey') }  
idConcept { specializes idThing,  
    Noun('concept', 'abstraction', 'conceptualization')}  
idAdjective { specializes idConcept,  
    Noun('adjective', 'descriptive', 'modifier', 'attribute', 'adnoun') }  
// Used for the state specified by stative propositions like "under_p_state" when they mean  
// "do x while being under y"  
idStativeEvent { specializes idConcept,  
    Noun('preposition')}  
idStativeEventPreposition { specializes idConcept }  
idStativeEventPrepositionRight { specializes idConcept }  
idAdverb {specializes idConcept,  
    Noun('adverb') }  
idName { specializes idConcept,  
    Noun('name', 'term', 'appellation', 'designation', 'moniker') }  
idCompoundName { specializes idName,  
    Noun('compound name') }  
idProperName { specializes idName,  
    Noun('proper name') }  
// For books the title is both a Proper Name and text that can be read  
idTitle { specializes idProperName, specializes idText,  
    Noun('title') }  
idText { specializes idPhysicalObject,  
    Noun('text', 'words', 'prose') }  
idCardinality { specializes idConcept,  
    Noun('number') }  
idOrdinality { specializes idConcept,  
    Noun('order') }  
  
// Adjectives  
idColor { specializes idAdjective,  
    Noun('color', 'hue', 'chroma', 'chromaticity', 'coloration', 'coloring', 'tint') }  
idSide { specializes idAdjective,  
    Noun('side', 'face') }  
idTexture { specializes idAdjective,  
    Noun('texture', 'consistency', 'quality', 'surface', 'coarseness', 'feel', 'roughness') }  
    idHeight { specializes idAdjective,  
    Noun('height', 'tallness') }  
// An openable face has a property of type OpenState  
idOpenState { specializes idAdjective,  
    Noun('open state') }  
// a property of type idOpenState can *also* have a mechanism that locks it  
// of type idLockMechanism.  The value will be an object that has a property of type idLockedState  
idLockMechanism { specializes idConcept,  
    Noun('lock mechanism')}  
  
// idMissingSideOpen/Closed are 'virtual' properties that can be on anything  
// since they are properties they need to be on *something* so we (hack) put them  
// on idOpenState since that's what they are instancesOf  
idMissingSideOpen { instanceOf idOpenState, propertyOf idOpenState, = 'open' }  
idMissingSideClosed { instanceOf idOpenState, propertyOf idOpenState, = 'closed' }  
// A lockable object has a property of type idLockedState  
idLockedState { specializes idAdjective,  
    Noun('locked state') }  
// The states of locked/unlocked must be defined on an object for us to be able to know what  
// locked and unlocked mean (since we look for a property with a value to see what kind of thing it is)  
// So we define them here  
idLockedStateLocked { instanceOf idLockedState, propertyOf idLockedState, = 'locked' }  
idLockedStateUnlocked { instanceOf idLockedState, propertyOf idLockedState, = 'unlocked' }  
  
// A lockable object can have a required tool property that is set to a thing  
// required to unlock it  
idRequiredTool { specializes idConcept }  
idNoTool { specializes idConcept,  
    Noun('nothing') }  
  
// The ID for an actor property of events  
idActor { specializes idConcept,  
    Noun('actor') }  
// The ID for an action property of events  
idAction { specializes idConcept,  
    Noun('action') }  
// The ID for the target of an event  
idTarget { specializes idConcept,  
    Noun('target') }  
idSubordinateClause { specializes idConcept,  
    Noun('subordinate clause') }  
  
// *******************************  
// **** Physics Basic Bounding Box  
// *******************************  
idPlace { specializes idThing,  
    Noun('place', 'area', 'region', 'site', 'spot', 'whereabouts', 'location') }  
  
// Physical objects can be manipulated  
// All Physical objects are a 'Place' since things can be on them, next to  
// them, etc  
idPhysicalObject { specializes idPlace,  
    Noun('physical object', 'object') }  
  
// A physical object can have 6 faces  
// each has a state (e.g. idLocalTop) which is which face they are  
idFace { specializes idPhysicalObject,  
    Noun('face') }  
  
// They have seven faces which are called 'local' since they don't change as the  
// object is moved.  They are used as states of an idFace  
idLocalTop { Noun('top', 'surface') }  
idLocalBottom { Noun('bottom', 'underside', 'base', 'underbelly', 'underneath') }  
idLocalLeft { Noun('left', 'port') }  
idLocalRight { Noun('right', 'starboard') }  
idLocalFront { Noun('front', 'forward') }  
idLocalBack { Noun('back', 'rear', 'backside', 'posterior', 'stern', 'aft') }  
idLocalInside { Noun('inside', 'interior') }  
  
// A face can have an opening which is a partOf that face  
idOpening { specializes idPhysicalObject,  
     Noun('opening', 'entrance', 'access', 'corridor', 'door', 'doorway', 'entry', 'entryway', 'hall', 'hallway', 'opening', 'passageway', 'passage', 'path', 'entranceway', 'entree', 'approach', 'gateway', 'portal', 'exit', 'outlet')}  
  
  
// ************************  
// **** Composition Model  
// ************************  
// There is an area of Logic called Mereology (https://en.wikipedia.org/wiki/Mereology) that formally defines this  
// Best description: Odell, J.J. Six different kinds of composition. Journal of Object Oriented Programming, 5 (8). 10-15.  
//           http://www.cs.sjsu.edu/~pearce/modules/lectures/ooa/references/domain/compkind.html  
//       Also this is a good overview:  
//           https://www.w3.org/2001/sw/BestPractices/OEP/SimplePartWhole/#ref-flavours-of-part-of  
//       Winston, M., Chaffin, R. and Hermann, D. A taxonomy of part-whole relations. Cognitive Science, 11. 417-444  
//       Artale, A., Franconi, E. and Pazzi, L. Part-whole relations in object-centered systems: An overview. Data and Knowledge Engineering, 20. 347-383  
//           http://www.inf.unibz.it/~artale/papers/appl-onto-07.pdf  
//       http://www.cs.sjsu.edu/~pearce/modules/lectures/ooa/domain/agg.htm    
// 'Combining' relationships: PartOf vs Touching vs Connected  
//   - Describe parts of and next to  
//   - All are independently nameable  
//   - They differ in whether they are positioned and separable  
//  
idCombiningRel { specializes idThing }  
  
// Positional relationships: idTouchingRel and idConnectedRel  
//   - they define a face where they are  
//   - Since they have a position, they are also a 'Place'  
idPositionalRel { specializes idCombiningRel }  
idPositionalRel { specializes idPhysicalObject }  
  
// Composite relationships: PartOf and Connected:  
//   - compose several things into one thing  
//   - If you move it, it moves together  
idCompositeRel { specializes idCombiningRel }  
  
// Touching relationships: two things that are next to each other  
//   - always between two idFaces  
//   - positioned, separate  
idTouchingRel { specializes idPositionalRel }  
  
// PartOf relationship is between a part and and the thing it is part of  
//   - Always between two objects  
//   - unpositioned, unseparable  
//   - Does not have a face connecting it, it has to have its own properties describing  
//    "how" it is part of if if required  
idPartOfRel { specializes idCompositeRel }  
  
// idConnectedRel: positioned, unseparable  
//    - Must be via a face  
//    - Should be one way where the minor thing is connected to the major thing  
idConnectedRel { specializes idPositionalRel }  
idConnectedRel { specializes idCompositeRel }  
  
idGreeting { specializes idConcept,  
     Noun('hi', 'hello', 'regards', 'howdy', 'hey') }  
  
  
// ****************************************  
// ****************************************  
// ****************************************  
// **** Specializations for this game  
// ****************************************  
// ****************************************  
// ****************************************  
  
  
// ********************************  
// **** Types of things that exist  
// ********************************  
idWorld { specializes idPhysicalObject,  
    Noun('world', 'universe') }  
idPerson { specializes idPhysicalObject,  
    Noun('person', 'character', 'human', 'individual', 'somebody')}  
idBook {specializes idPhysicalObject,  
    Noun('book', 'album', 'booklet', 'manual', 'pamphlet', 'publication', 'tome', 'volume')}  
idPage {specializes idPhysicalObject,  
    Noun('page', 'paper')}  
idHand { specializes idPhysicalObject,  
    Noun('hand', 'fist', 'palm')}  
idCrystal { specializes idPhysicalObject,  
    Noun('crystal', 'gem', 'gemstone', 'stone', 'rock')}  
idTable { specializes idPhysicalObject,  
    Noun('table')}  
idSafe { specializes idPhysicalObject,  
    Noun('safe', 'vault', 'strongbox')}  
idLock { specializes idPhysicalObject,  
    Noun('lock')}  
// Needs to have both Noun (for describing) and CompoundPrefix (for finding an object of this type)  
// TODO: We could extend nameOf() to just look this all up but it is more work  
idLockFaceBase { specializes idPhysicalObject,  
    Noun('face')}  
idLockFace { specializes idLockFaceBase,  
    CompoundPrefix(idLock), Noun('lock face')}  
idDiamondCave {specializes idCave,  
    CompoundPrefix(idDiamond), Noun('diamond cave')}  
idKey { specializes idPhysicalObject,  
    Noun('key')}  
idKeyhole { specializes idPhysicalObject,  
    Noun('keyhole')}  
idDiamond { specializes idPhysicalObject,  
    Noun('diamond', 'jewel', 'gem', 'precious_stone', 'ice', 'stone', 'rock', 'adamant', 'carbon', 'transparent_gem', 'carbonado', 'rhombus')}  
idRock { specializes idPhysicalObject,  
    Noun('rock', 'boulder', 'stone', 'pebble')}  
idCave { specializes idPhysicalObject,  
    Noun('cave', 'cavern', 'grotto', 'room') }  
idFireman { specializes idPerson,  
    Noun('fireman')}  
  
// ************************  
// **** Items in the world  
// ************************  
idLexi { instanceOf idPerson, .idProperName = 'Lexi', Face(Bottom, Left, Right)}  
idLexiLeftHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'left')}  
idLexiRightHand { instanceOf idHand, Face(Back, Inside), Adjective(idSide, 'right')}  
idCrystal1 { instanceOf idCrystal, Face(Bottom) }  
idKey1 { instanceOf idKey, Face(Bottom),  
    Adjective(idColor, 'red', 'reddish', 'cardinal', 'coral', 'crimson', 'flaming', 'maroon', 'rose', 'burgundy', 'carmine', 'cerise', 'cherry', 'fuchsia', 'garnet', 'magenta', 'pink', 'ruby', 'russet', 'rust', 'salmon', 'sanguine', 'scarlet', 'vermilion', 'rosy', 'rubicund', 'ruddy', 'rufescent')}  
idSafe1 { instanceOf idSafe, Face(Inside, Left, Right, Back, Front, Bottom, Top) }  
idSafe1Top {  
    Adjective(idOpenState, 'open'),  
    .idLockMechanism = idLock1 }  
idLock1 { instanceOf idLock, Face(Back, Front),  
    Adjective(idLockedState, 'unlocked') }  
idLockFace1 { instanceOf idLockFace, Face(Back, Front) }  
idKeyhole1 { instanceOf idKeyhole, Face(Inside, Back) }  
idBook1 { instanceOf idBook, Face(Bottom, Top, Inside),  
    .idTitle = 'How to Escape the Cave System',  
    .idOpenState = 'open' }  
idBook1Page1 { instanceOf idPage, Face(Bottom), .idCardinality = '1', .idOrdinality = '1'}  
idBook1Page1Text { instanceOf idText, idPartOfRel idBook1Page1,  
    = 'dedicated to the DELPH-IN team' }  
idBook1Page2 { instanceOf idPage, Face(Bottom), .idCardinality = '2', .idOrdinality = '2'}  
idBook1Page2Text { instanceOf idText, idPartOfRel idBook1Page2,  
    = 'If you get lost in this cave, you really only need to do one very simple thing.' }  
idBook1Page3 { instanceOf idPage, Face(Bottom), .idCardinality = '3',  
    .idUnknownObject = true }  
idBook1Page3Text { instanceOf idText,  
    idPartOfRel idBook1Page3,  
    = 'To exit the cave yell `I am free!` loudly' }  
idTable1 { instanceOf idTable, Face(Top, Bottom)  }  
idDiamond1 { instanceOf idDiamond, Face(Bottom) }  
idRock1 { instanceOf idRock, Face(Bottom),  
    Adjective(idColor, 'blue', 'blueish', 'bluish', 'azure', 'beryl', 'cerulean', 'cobalt', 'indigo', 'navy', 'royal', 'sapphire', 'teal', 'turquoise', 'ultramarine'),  
    .idDisplayName = 'a smooth blue rock',  
    Adjective(idTexture, 'smooth', 'uniform')}  
idRock2 { instanceOf idRock, Face(Bottom),  
    Adjective(idColor, 'red', 'reddish', 'cardinal', 'coral', 'crimson', 'flaming', 'maroon', 'rose', 'burgundy', 'carmine', 'cerise', 'cherry', 'fuchsia', 'garnet', 'magenta', 'pink', 'ruby', 'russet', 'rust', 'salmon', 'sanguine', 'scarlet', 'vermilion', 'rosy', 'rubicund', 'ruddy', 'rufescent'),  
    .idDisplayName = 'a rough red rock',  
    Adjective(idTexture, 'rough', 'bumpy', 'coarse', 'unfinished')}  
// Needs to have both Noun (for describing) and CompoundPrefix (for finding an object of this type)  
idEntrancecave { instanceOf idDiamondCave,  
    Face(Inside, Bottom, Back)}  
idEntrancecaveBackOpening { instanceOf idOpening, idPartOfRel idEntrancecaveBack,  
    Adjective(idHeight, 'tall', 'big'),  
    .idDisplayName = 'a tall passage to a cave named `Plage`',  
    .idOpenState = 'open' }  
idPlage { instanceOf idCave, .idProperName = 'Plage', Face(Inside, Bottom, Front, Back) }  
idPlageFrontOpening { instanceOf idOpening, idPartOfRel idPlageFront,  
    Adjective(idHeight, 'tall', 'big'),  
    .idDisplayName = 'a tall passage to the diamond cave',  
    .idOpenState = 'open'}  
idPlageBackOpening { instanceOf idOpening, idPartOfRel idPlageBack,  
    Adjective(idHeight, 'short', 'low', 'small', 'compact', 'squat', 'wee'),  
    .idDisplayName = 'a short door to the green cave',  
    .idOpenState = 'open'}  
idGreenRoom { instanceOf idCave, Face(Inside, Bottom, Front),  
    .idDisplayName = 'a green cave',  
     Adjective(idColor, 'green')}  
idGreenRoomFrontOpening { instanceOf idOpening, idPartOfRel idGreenRoomFront,  
    Adjective(idHeight, 'short', 'low', 'small', 'compact', 'squat', 'wee'),  
    .idDisplayName = 'a short door to Plage',  
    .idOpenState = 'open'}  
idSafe2 { instanceOf idSafe, Face(Inside, Left, Right, Back, Front, Bottom, Top) }  
idSafe2Inside { .idUnknownObject = true }  
idSafe2Top {  
    Adjective(idOpenState, 'closed'),  
    .idLockMechanism = idLock2 }  
idLock2 { instanceOf idLock, Face(Back, Front, Inside),  
    Adjective(idLockedState, 'locked') }  
idLock2_prop_idLockedState { .idRequiredTool = idKey1 }  
idLockFace2 { instanceOf idLockFace, Face(Back, Front) }  
idKeyhole2 { instanceOf idKeyhole, Face(Inside, Back) }  
idWorld1 { instanceOf idWorld, Face(Inside) }  
  
// ******************************  
// **** Item Layout  
// ******************************  
  
// *****************************************************************  
// | |                       | idCrystal1  | |                     |  
// | |                       --- idSafe1 --- |                     |  
// | |                           idBook1     |                     |  
// | |  idLexi   idDiamond1      idTable1    |     idRock1 idRock2 |  
// | |------------- idEntrancecave ----------| ------ idPlage -----|  
// | -------------------------------- idWorld1 --------------------|  
// ***************************************************************  
//                                                   |--------------|  
//  |--------------|---------------|---------------| |--------------|  
//  |- idKeyhole1 -|- idLockFace1 -|--- idLock1 ---| |-- idSafe1 ---|  
//  |--------------|---------------|---------------| |--------------|  
//                                                   |--------------|  
// *****************************************************************  
//                               | -----------------|  
//           | -----------------|| -----------------|| -----------------|  
//           |  idLexiRightHand || ---- idLexi -----|| --idLexiLeftHand-|  
//           | -----------------|| -----------------|| -----------------|  
//                               | -----------------|  
// ***************************************************************  
  
// NOTE: Arranging items from top to bottom makes them get returned in this order to  
// which is useful for printing results  
// Arrangement of items in Diamond Cave  
idCrystal1Bottom { idTouchingRel idSafe1Inside }  
idSafe1Bottom { idTouchingRel idBook1Top }  
idLock1Back { idConnectedRel idSafe1Front }  
idLockFace1Back { idConnectedRel idLock1Front }  
idKeyhole1Back { idConnectedRel idLockFace1Front }  
idBook1Bottom { idTouchingRel idTable1Top }  
idTable1Bottom { idTouchingRel idEntrancecaveInside }  
idDiamond1Bottom { idTouchingRel idEntrancecaveInside }  
idLexiBottom { idTouchingRel idEntrancecaveInside }  
idLexiLeftHandBack { idConnectedRel idLexiLeft }  
idLexiRightHandBack { idConnectedRel idLexiRight }  
idEntrancecaveBottom { idTouchingRel idWorld1Inside }  
idEntrancecaveBack { idTouchingRel idPlageFront }  
idGreenRoomBottom { idTouchingRel idWorld1Inside }  
idPlageBack { idTouchingRel idGreenRoomFront }  
idKey1Bottom { idTouchingRel idGreenRoomInside }  
idBook1Page1Bottom { idConnectedRel idBook1Inside }  
idBook1Page2Bottom { idConnectedRel idBook1Inside }  
idBook1Page3Bottom { idTouchingRel idSafe2Inside }  
idSafe2Bottom { idTouchingRel idGreenRoomInside }  
idLock2Back { idConnectedRel idSafe2Front }  
idLockFace2Back { idConnectedRel idLock2Front }  
idKeyhole2Back { idConnectedRel idLockFace2Front }  
  
// Arrangement of items in Plage  
idRock1Bottom { idTouchingRel idPlageInside }  
idRock2Bottom { idTouchingRel idPlageInside }  
idPlageBottom { idTouchingRel idWorld1Inside }