Skip to content

Core Action Modules

Sean Trott edited this page Jun 17, 2016 · 11 revisions

(Core Action Modules)

Problem Solver

The desired functionality of a system will change considerably across applications; thus, the Core Problem Solver (file) defines a general interface, mode for unpacking and routing n-tuples, and sending n-tuples back to the UI-Agent, but does nothing with the unpacked n-tuples, other than printing them to the screen. Once an application is identified (E.g., MORSE, ROS, KARMA), the integrator can provide the bindings / API calls from the solver to the application. The simplest form of the solver will just unpack n-tuples and make API calls. A more complex solver will add functionality to the application (additional path planning, reasoning, etc.).

Importantly, the Core Problem Solver subclasses the Core-Agent, meaning it can subscribe to channels with a callback function, and initializing the Agent resembles the following command:

python core_solver.py ProblemSolver

The Core Problem Solver does define a clean system by which n-tuples are received, unpacked, and routed -- the unpacking of an n-tuple mirrors its compositional construction. This routing can be seen as a series of dispatches, as the solver recursively unpacks the n-tuple.

World/Situation Model

A crucial aspect of the Problem Solver is an internal world model. This world model is formed using both data from the application (vision, "event information", etc.) and from linguistic input (the user making assertions about world states). The model is used to locate the referents of described objects, plan actions, and carry out tasks.

The Core Problem Solver doesn't have an actualized world model, since there's no underlying application or world to model, but it does define an attribute in its initialization:

self.world = []

... As well as a method for updating this world model:

update_world(self, discovered=[])

This method adds discovered items to the world model. Of course, both this method and the attribute should be extended or overridden for a given application.

Key methods

This is the callback method that the Core Problem Solver uses when it receives methods from the UI-Agent. By default, the Core Problem Solver then calls solve (see below).

This is the main method that the Core Problem Solver defines, and the point at which the solver begins to recursively unpack the n-tuple. The outer wrapping of the n-tuple is a mood template, so the Problem Solver extracts the predicate_type (e.g., "assertion", "command", etc.), and then dispatches the n-tuple using this predicate_type (see below).

The Core Problem Solver has five known predicate types (recall that these values are shared between the language and action sides via the shared ontology):

  • command
  • query
  • assertion
  • conditional_imperative
  • conditional_declarative

In core, each of these corresponding methods simply prints out the n-tuple to the screen. In our robot application, however, the n-tuple is then unpacked and routed further (see below).

Note: the bottom two predicate types (conditional_imperative and conditional_declarative) are reframings of the "command" and "assertion" predicate types, but the solution is necessarily distinct, because a condition must be evaluated (usually using the methods used in solving a yes/no query) before evaluating the command/assertion.

This method should be called by an implementation of solve{predicate_type}_, as seen above. It takes in a filled-in event template, as well as a predicate type.

The method then extracts any available event-features from the n-tuple, such as modality ("can"), duration ("for 2 hours"), or telicity ("bounded"), and sets them to a field attribute, for later use. Then, the Solver continues to unpack the n-tuple, sending the contents of this n-tuple's parameter template to the route_action method.

Note: Occasionally, route_action will return a value, such as a message to be sent back to the UI-Agent, or an "error-descriptor" (in the case of an impossible command). In that case, this method handles sending back n-tuples to the UI-Agent.

This method takes in the contents of a parameter template, as well as a predicate type.

Before inspecting the type of action specified, the Solver determines whether it's dealing with a particular type of "complex process" (such as a serial or causal process). In that case, the n-tuple is routed to the corresponding method (see solve_serial below).

Otherwise, the Solver extracts the actionary from the parameters, such as @move or @push. It then finds the corresponding method to solve the parameters using the actionary and the predicate_type, such as:

command_move(parameters)
query_move(parameters)
assertion_move(parameters

The implementation of these methods is entirely application-dependent, but as a best-practice, the Robot Problem Solver uses a get{actionary}info method, to avoid redundancies in each method:

get_move_info(parameters)

Calls dispatch on parameters. In general, this is just called by route_action, but this abstraction here serves the purpose of allowing route_dispatch to put the dispatch function on a command queue, like so:

self.commandQ.put((self.priority, self.get_tiebreaker(), (dispatch_function, parameters)))

The above command is in the QueueSolver, which inherits from the CoreProblemSolver. This isn't necessary for most purposes, but is relevant for those interested in X-nets.

This method takes in an n-tuple representing a serial process (e.g., "push the blue box north, then push the green one south"), and solves the component processes in order ("process1", then "process2") by calling route_action. Ordering is specified by the grammar of the sentence, as seen below;

do X BEFORE doing Y
process1: X
process2: Y

do X AFTER doing Y process: Y
process2: X

Note: The grammar also encodes serial processes recursively, so one of the component processes could also be a serial process. The Solver handles this by calling route_action on both processes in order, which will detect whether one or both of the processes is also a serial process (see above).

Sends an n-tuple containing message back to the UI-Agent's address, tagged as a response to a query.

Sends an n-tuple containing message back to the UI-Agent's address, tagged as an id_failure, e.g.:

Sorry, I don't know what you mean by 'the blue box'.

Sends an n-tuple back to the UI-Agent's address, containing both a message requesting clarification, as well as the ambiguous n-tuple; the ambiguous part of the n-tuple should be tagged with a *. The message might resemble:

which red box?

Sends an n-tuple back to the UI-Agent's address, containing a message tagged as an "error_descriptor". The UI-Agent for a given domain might have special procedures for cases in which the Problem Solver could not solve the problem presented to it. In the Core UI-Agent, it simply prints the message to the screen:

FED1_ProblemSolver: I cannot push 'the blue box' because I don't have enough fuel.

Synchronous vs. Asynchronous N-tuple Passing

By default, the Transport's callback system operates asynchronously -- when an n-tuple is received on a particular channel, the given callback method is executed immediately.

However, certain applications may require that a condition be met before unpacking an n-tuple; potentially, if multiple Problem Solvers (Agents) are active and communicating with each other, they might like to coordinate their actions. We have provided example code for this type of coordinated planning, including a boss agent and a worker agent.