ERG to Logic prototype

With help from many in this Discourse (thank you!), I’ve got a simple prototype working that uses the ERG to parse English into a logic form and a Prolog engine to process the result. I thought I’d show you all what I got working! First is a “demo” and then an overview of how it works.

Example Transcript

Here’s an example interaction. Everything after a “?” is something I typed, the responses follow. The “scenario” is a cave system with two caves connected to each other. First cave is an entrance but it doesn’t have a name, second cave is called Plage. There is a diamond in the entrance cave and two rocks in Plage. It also knows about one pronoun (You), and a few facts about the world in general.

? Where are you?
Where = entrancecave

? What do you see?
What = diamond1

? Do you see a rock?

? Where is the rock?
there is more than one of those

? Where is a rock?
Where = plage

? go to Plage

? do you see a rock?

? are you a person?

? are you a Donkey?

? Where are the Donkeys?
there aren’t any

? is the diamond in the entrance?

System Overview

There is a relatively simple translation layer that takes the predicate logic output of the ERG and converts it to Prolog terms. Obviously, I had to define each of these terms in Prolog. As an example:

“Where is the diamond?”

is translated into this logical form by doing a depth-first walk of one of the solutions output by the ERG:

Logic: _the_q(x3, _diamond_n_1(x3), which_q(x4, place_n(x4), loc_nonsp(e2, x3, x4)))

which gets converted into this Prolog form by doing some mappings:

Prolog: =(CreateOrEval, create), =(E9052, id9056), d_the_q(conj(d_noun(diamond, X9053), conj(isInstance(X9053)))), d_noun(place, X9054), d_loc_nonsp(E9052, X9053, X9054, CreateOrEval).

  • Non-quantifiers are trivial mappings
  • Quantifiers are more complicated. For example “_the_q” is translated to a predicate called “d_the_q()”
    • which fails if it gets more than one thing
    • and takes the RSTR in conjunction with a predicate called “isInstance”
    • isInstance() ensures results are limited to only actual things in the world, not types in the type hierarchy.
    • the predicate “conj()” is just doing boolean conjunction with its arguments
  • Probably the least obvious thing is how I’m handling events (still a work in progress). I’m assigning a GUID like “id9056” to each event variable before I send the statement to Prolog. Then, the terms can “do whatever they need to do” with the event.
    • In this example, they don’t do anything, but if a group like “very green” was there, “very” would attach some data to the event saying the event was “very”, and then later “green” would see that its event had a degree specified and do the right thing with its x variable.
    • “CreateOrEval” is used to tell predicates with event arguments whether they should create or lookup the eventID in the database. Just an implementation detail.

Then, for questions and propositions, the Prolog is just executed and I get a result like below:

CreateOrEval = “create”, E9052 = “id9056”, X9053 = “diamond1”, X9054 = “entrancecave”

For questions with unbound variables like X9054, I just return those as the answer. If it had all bound variables, it would be yes or no depending on if it was true or not.

Commands like “go to X” get dealt with in two passes:

  • First pass is just like the above except there is a verb in there somewhere that doesn’t do anything except attach “verb X happened” to the event. Now we have a fully formed event that says something happened in the database.
  • Second pass is where “code” runs that grabs that EventID and figures out how to modify the world to make whatever got attached to it be true. If it works, the world is changed and that EventID stays around as a thing that happened that can be asked about.

Anyway, pretty cool to see things working. Next up I’ll be using the ERG to generate more interesting answers!

Thanks for sharing! It’s nice to see the MRSs being applied in this way. When I was a masters student I wanted to improve an existing open-source text-based-adventure engine to use MRS for more complicated commands, but when I tested the existing system I was actually pretty impressed with what it could do with regex-based pattern matching and templatic query structures. I’d like to see what you can do with this that isn’t easily possible with the shallower systems.

I really didn’t get all the details. From where did you get the mapping =(E9052, id9056)? Can you say more about the architecture?

Indeed, that is an interesting question. BTW, @EricZinda, are you aware of the work from @Dan that translate FOL to English? See, maybe the rules can give some ideas for the other direction: English to FOL.

Thanks for the pointer @arademaker, I am actually working on FOL to English generation right now so I’ll definitely dig into Dan’s work.

I’ll definitely be writing a series of blog entries on this once I’ve gotten to a good proof of concept point, but here’s the jist of the =(E9052, id9056) mapping. A little long, but I’m not sure how to explain it more shortly:

If a person says:

“Where is a dog?”
_the_q(x3, _dog_n_1(x3), which_q(x4, place_n(x4), loc_nonsp(e2, x3, x4)))

the FOL processing of this is something like:

  1. Take all the things in the world put them in x3, then filter x3 down to the set of things that have the property “dog”
  2. Now, take all the things in the world and put them in x4 and filter it down to just “places”
  3. Finally: filter both x3 and x4 down to just the items that have the relationship “loc_nonsp” which I take to mean “x3 has a location x4”
  4. x4 now contains the answer (if one existed). Ignore e2 for now (see below)

But what if a person says:

“There is a dog in the kitchen.”
_a_q(x4, _the_q(x10, _kitchen_n_1(x10), and(_dog_n_1(x4), _in_p_loc(e9, x4, x10))), _be_v_there(e2, x4))

We can treat it like a question and process it just like the "Where is a dog?" and, when done, we will get a "True" or "False" value which we can answer with "Yes, there is!"or "No, there isn't".

But what if we want to take it as “new knowledge” and add it to our list of facts about the world (I know there are lots of problematic things here, but bear with me)? We need to process it differently. We’ll need to create two new "things in the world" and set their properties:

  1. Create a thing, let’s call it "id1" and give it the "kitchen" property
  2. Create a second thing, let’s call it "id2", and give it the "dog" property
  3. Create the "id2 has location id1" association

After all that, if you ask the question "Is there a dog in the kitchen?" it will find those things and return "yes, there is!"

To actually "Create a thing called id1" in Prolog, you’ve got to represent it as a new "term" like "id1". Since the term is used in several places, you need to assign it to a variable like this:

=(X10, id1), _kitchen_n_1(X10), … [etc.]

but that is going to just query if there is a thing "id1" that has the property "kitchen". We really want it to create such a thing. So (for ease of implementation) I chose to give my Prolog predicates the ability to query or create things by taking an argument “CreateOrEval”, and then setting that argument to indicate if we are querying or creating, like this:

=(CreateOrEval, create) =(X10, id1), _kitchen_n_1(X10, CreateOrEval), … [etc.]

That statement will create the thing called "id1" in the database and give it the property "kitchen". So, after that, if you ask the system "Is there a dog in the kitchen" it will return "Yes, there is"

Again, hand waving details here but this is the basic approach to querying facts and creating them.


OK, so now I can explain events. What if a person says:

“The dog went slowly”
_the_q(x3, _dog_n_1(x3), and(_slow_a_1(e8, e2), _go_v_1(e2, x3)))

  1. Processing of "x3" is just like above
  2. How to process the "e8" and "e2" variables? Clearly these aren’t of the "Take all the things in the world and filter" variety, because then they’d have a quantifier and an "x-type" variable, right?

Actually, the way I am currently modeling the "e" variables (for better or worse) is to say that events like "e8" and "e2" really are just "things in the world" which have "properties" in much the same way as "dog" did.

  1. The "e2" event has the property "going"
  2. The "e8" event has the property "slow"
  3. "e2" and "e8" are related via the "_slow_a_1(e8, e2)" predicate.

I think it is correct to say I am modeling them as “existentially qualified”.

So, if, like our first example above, we treat this like a question (and we are going to just check if this is true), we’d process the phrase something like this:

_the_q(x3, _dog_n_1(x3), and(_slow_a_1(e8, e2), _go_v_1(e2, x3)))

  1. Filter "x3" to be things with the property "dog" like above
  2. "_slow_a_1" filters "e8" to be things with the property "slow" and filters "e2" to just those that have an association with whatever remains after filtering "e8"
  3. "_go_v_1" further filters "e2" to be things with the property "going" that have an association with any of the "dogs" in "x3"

If there was anything that remains in the set after step 3, we return “Yes, it did!”

But what if you are telling me (not asking) that "The dog went slowly". Then we’d do the same trick we did above and create all the "things" that make this true, like this:

=(CreateOrEval, create), =(X3, id1), =(E8, id2), =(E2, id3), _dog_n_1(X3, CreateOrEval), _slow_a_1(E8, E2, CreateOrEval)…[etc.]


Finally, what if you are telling me to do something like this:

Go to the Plage
_the_q(x9, named(Plage, x9), pronoun_q(x3, pron(x3), and(_to_p_dir(e8, e2, x9), _go_v_1(e2, x3))))

Commands are a little more subtle (at least they way I’m treating them) because they mix together querying the state of the world (for the objects in the command like "the Plage") and creating new things in the world (for the events that I am about to attempt like "go"). We don’t want to create "the Plage", we just want to go there and we need to make sure it is actually a thing that exists.

Right now, for commands, I am simply treating all the "x" variables as existing item queries and all the "e" variables as new item creations.

A command is telling me to attempt to make the fact "I went to the Plage" be true (in the future) by actually doing that thing.

So for this example, the actual Prolog that gets generated is:

=(CreateOrEval, create), =(E9059, id9064), =(E9061, id9065), d_pronoun(you, X9062), d_the_q(conj(d_named(‘Plage’, X9063), conj(isInstance(X9063)))), d_to_p_dir(E9059, E9061, X9063, CreateOrEval), d_go_v_1(E9061, X9062, CreateOrEval).

Which will return False if there is not a thing called “Plage” that already exists, but which actually creates "thing id9064" to represent the "going" event that I’m about to try.

If this whole statement succeeds, it means all the "real world x items" existed in the world, so we can attempt to actually do the verb that was requested.

So, finally, I actually run code which actually moves the player. If it succeeds, the world state is changed and the record of the event "id9064" hangs around so you can ask if I did that later. If it fails, I revert the memory of "id9064" and tell the user I failed.

Hopefully, that makes sense…Probably more than you wanted to know…

1 Like