dlvhex
2.5.0
|
The dlvhex reasoner evaluates Answer Set Programs with external atoms. One important design principle was to provide a mechanism to easily add further external atoms without having to recompile the main application. A plugin is a shared library that provides functions to realise custom external atoms. Furthermore, a plugin can supply rewriting facilities, which may alter the input logic program prior to evaluation. Plugins can be written in Python as described below or in C++ as described here; the former does not have all features of the C++ version but should be sufficient in almost all cases.
This Section gives an overview of dlvhex Python plugins and external atoms.
Formally, an external atom is defined to evaluate to true or false, depending on a number of parameters:
However, it is more intuitive and convenient to think of an external atom not as being boolean, but rather functional: Depending on a given interpretation and a list of input constants, it returns output tuples. For instance, the external atom to import triples from RDF files has this form:
&rdf[uri](X,Y,Z)
where uri
stands for a string denoting the RDF-source and X
, Y
, and Z
are variables that represent an RDF-triple. The function associated with this atom simply returns all RDF-triples from the specified source. Obviously, in this case the interpretation is ignored.
The interface that is used by dlvhex to access a plugin follows very closely these semantics. For each atom, a retrieval function has to be implemented, which receives a query-object and has to return an an answer-object. The query-object carries the input interpretation as well as the ground input parameters of the external atom call, while the answer object is a container for the output tuples of the external atom's function.
Theoretically, it is completely up to the atom function how to process the interpretation together with the input constants, which are basically only names. In practice however, only parts of the interpretation might be needed (if at all). Considering this as well as for efficiency reasons, we created two (in the implementation three, see below) categories of input parameters:
A parameter of type "Constant" is not related to the interpretation at all, like in the previous example of the RDF-atom. A parameter is of type "Predicate" means that all facts in the interpretation with this predicate are necessary for the atom. Let's assume, we have an external atom that calculates the overall price of a number of books given by their ISBN number:
&overallbookprice[isbn](X)
The single input parameter of this atom would be of type "Predicate", meaning that not the constant itself is necessary for the atom's function, but the part of the interpretation with this predicate. So if we have, e.g.,
the atom's function will be called with a "reduced" interpretation:
Specifying the type of input parameters not only helps to single out the relevant part of the interpretation, but also supports dlvhex in calculating the dependencies within a HEX-program.
We wanted to keep the interface between dlvhex and the plugins as lean as possible. Necessary tasks are:
The register function has the following form:
def register(): dlvhex.addAtom ("concat", (dlvhex.CONSTANT, dlvhex.CONSTANT), 1) )
It adds one entry for each external atom. Each entry is again a tuple of arity 3: the first element is the external predicate name, the third element is the output arity, and the second is another tuple of input parameter types (dlvhex.CONSTANT, dlvhex.PREDICATE, dlvhex.TUPLE).
Each external predicate name (e.g. concat) needs to be implemented in form of another Python function with an appropriate number of input parameters.
Example:
def concat(a, b): dlvhex.output((dlvhex.getValue(a) + dlvhex.getValue(b), ))
The function just takes the values of these parameters and outputs their string concatenation. Here, a and b are the input parameters (of type constant). If an external atom specifies an input parameter of type TUPLE, the elements will be passed as a Python tuple.
Example:
def concat(tup):
ret = ""
for x in tup:
ret = reg + x
dlvhex.output((ret, ))
Note that akin to the C++ API, terms and atoms are represented by IDs and the retrieval of the value behind usually requires the use of the getValue method; some methods combine this with other functionalities (see method list below).
If an external atom specifies that it provides partial answers (see below), it further must add all tuples which can become true if currently unassigned input atoms are defined using
dlvhex.outputUnknown((ret, ))
. As this might be the case for infinitely many tuples, the (finite) set of relevant ones can be retrieved from the output atoms returned by
dlvhex.getRelevantOutputAtoms()
.
In addition to the actual semantics of an external atom, the Python API can also be used for defining custom learning techniques (described in the following list). Advanced plugin features, such as providing converters, rewriters and dependency graph optimizations. are, however, only possible with the C++ API.
In more detail, the dlvhex Python module provides the methods described in the following paragraphs.
Management of dlvhex IDs
{.txt}tuple getTuple(aID)
{.txt}tuple getTupleValues(aID)
{.txt}string getValue(id)
{.txt}int getIntValue(id)
{.txt}string getValue(tup)
{.txt}tuple getExtension(id)
{.txt}dlvhex.ID storeString(str)
{.txt}dlvhex.ID storeInteger(int)
{.txt}dlvhex.ID storeAtom(args)
{.txt}dlvhex.ID negate(aID)
{.txt}void addAtom(name, args, ar, [prop])
{.txt}void storeExternalAtom(pred, input, output)
Basic Plugin Functionality
{.txt}void output(args)
{.txt}void outputUnknown(args)
dlvhex.getRelevantOutputAtoms()
{.txt}ID getExternalAtomID()
{.txt}tuple getInputAtoms([pred])
{.txt}tuple getRelevantOutputAtoms([pred])
prop.providesPartialAnswer() \code): the external atom is expected to mark all tuples of the output atoms as unknown if they might become true under a more complete input.</li> <li>\code{.txt}tuple getTrueInputAtoms([pred])
{.txt}int getInputAtomCount()
{.txt}int getTrueInputAtomCount()
{.txt}bool isInputAtom(id)
{.txt}bool isTrue(id)
{.txt}bool isFalse(id)
{.txt}void resetCacheOfPlugins()
Conflict-driven Learning
Usually, learned nogoods consist of 1. a set of positive or negated input atoms, and 2. a negative output atom, where 1. is the justification for the (positive) output atom to be true. Note that a nogood is a set of atoms which must not be all simultanously true. Therefore, this encodes that whenever all atoms from 1. are true, then the output atom must not be false (this is why the negative output atom is added).
{.txt}bool learn(tup)
{.txt}bool learnSupportSets()
{.txt}ID storeOutputAtom(args, [sign])
For instance, the nogood { p(a), -q(a), -&diff[p,q](a) } encodes that whenever the atom p(a) is true and the atom q(a) is false, then the atom &diff[p,q](a) must be true (i.e. must not be false) since constant a will be in the output of the set difference of p and q.
Inremental External Query Answering
{.txt}bool isAssignmentComplete()
{.txt}bool isAssigned(id)
{.txt}bool hasChanged(id)
{.txt}ID storeRule(head, pbody, nbody)
Subprogram Evaluation
{.txt}tuple evaluateSubprogram(tup)
{.txt}facts, rules
{.txt}tuple loadSubprogram(filename)
{.txt}(edb, idb)
External Source Properties Declaration
An instance of dlvhex.ExtSourceProperties can be passed to the addAtom method as last parameter to specify properties of the external atom, which might help the reasoner to speed up evaluation. The structure can be configured using the following methods:
{.txt}void prop.addMonotonicInputPredicate(index)
{.txt}void prop.addAntimonotonicInputPredicate(index)
{.txt}void prop.addPredicateParameterNameIndependence(index)
{.txt}void prop.addFiniteOutputDomain(index)
{.txt}void prop.addRelativeFiniteOutputDomain(index1, index2)
{.txt}void prop.setFunctional(value)
{.txt}void prop.setFunctionalStart(index)
{.txt}void prop.setOnlySafeSupportSets(value)
{.txt}void prop.setSupportSets(value)
{.txt}void prop.setCompletePositiveSupportSets(value)
{.txt}void prop.setCompleteNegativeSupportSets(value)
{.txt}void prop.setVariableOutputArity(value)
{.txt}void prop.setCaresAboutAssigned(value)
{.txt}void prop.setCaresAboutChanged(value)
{.txt}void prop.setAtomlevellinear(value)
{.txt}void prop.setUsesEnvironment(value)
{.txt}void prop.setFiniteFiber(value)
{.txt}void prop.addWellorderingStrlen(index1, index2)
{.txt}void prop.addWellorderingNatural(index1, index2)
{.txt}void prop.providesPartialAnswer(values)
dlvhex.output
dlvhex.outputUnknown
Moreover, for an ID object id, there are the following shortcuts:
In order to load a Python-implemented plugin stored in file PATH, pass the additional option
--pythonplugin=PATH
to dlvhex.