Python Interfaces For OpenCog Framework API
From OpenCog
Welcome to the overview for the Python bindings for OpenCog.
Written by Joel Pitt <joel@opencog.org> - feedback encouraged.
Requirements:
- Python 2.6 or later - these bindings may work with earlier version, but they have not been tested at all.
- Cython 0.14 or later. http://www.cython.org/
- Nosetests - for running unit tests.
Both Cython and Nosetests can be installed with easy_install:
sudo easy_install cython nose
The bindings are written mostly using Cython, which is an awesome language for writing code that looks pythonic but gets compiled to C (and then machine code by gcc or your favourite compiler). It also makes it trivial to access both Python objects and C objects without using a bunch of extra Python library API calls.
Currently the package structure looks like this:
opencog.atomspace opencog.atomspace.types opencog.cogserver
Eventually, when other components of OpenCog are accessible, they'll follow a similar pattern:
opencog.pln opencog.rules opencog.moses
PLN for Python is available at opencog/python/logic.py
Contents |
Tutorial
This tutorial is a first look at the Python bindings. It assumes that you've got a good grasp on the concept of the AtomSpace and the CogServer. Oh, and it helps to know a bit of Python too!
Setting up
Go through the normal process of building OpenCog. Then ensure that the OpenCog data directory is in your Python sys.path. By default the opencog python module lives at /usr/local/share/opencog/python when you do "make install", and you can modify your PYTHONPATH environment variable to ensure Python checks that location. If you just want to use your build dir you can use something like:
$ export PYTHONPATH=/home/joel/src/opencog/bin/opencog/cython
AtomSpace API
One great thing about these bindings is that they let you interact and instantiate your own AtomSpace objects interactively. I recommend IPython, which is a very capable Python shell. Prior to this, the only interactive way of modifying the AtomSpace was using the Scheme bindings embedded in an active CogServer, and even then you'd only be able to modify the core CogServer AtomSpace.
Without further ado, here's how to add a node:
>>> from opencog.atomspace import AtomSpace, types >>> a = AtomSpace() >>> a.add_node(types.ConceptNode, "My first python created node") <opencog.atomspace.Atom object at 0x203fa80>
Of course, to make things more succinct when referring to types you can alias stuff:
>>> t=types >>> a.add_node(t.ConceptNode, "Ah, more concise") >>> ConceptNode = t.ConceptNode >>> a.add_node(ConceptNode, "Ah, a bit more concise")
You'll notice these return opencog.atomspace.Atom objects, which internally store a Handle with a UUID and the AtomSpace it's connected to:
>>> atom = a.add_node(ConceptNode, "handle bar") >>> str(atom.h) '<UUID:4>' >>> atom.atomspace <opencog.atomspace.AtomSpace object at 0x203220a>
Or you may get a different UUID. The atom will fetch and cache information about itself behind the scenes.
>>> str(an_atom) 'node[ConceptNode:handle bar]' >>> an_atom.long_string() 'node[ConceptNode:handle bar] av:(0,0) tv:([0.000000,0.000000=0.000000])' >>> an_atom.name 'handle bar' >>> str(an_atom.t) # can also use an_atom.type if you want to be verbose '3' >>> an_atom.type_name 'ConceptNode'
Note that on the second reference to an_atom.t, it won't recheck the AtomSpace for the atom type because it's immutable and gets cached internally. If you try to set an immutable property of an Atom, you get an AttributeError exception:
>>> an_atom.name = 'change your name man, it sucks.' AttributeError: attribute 'name' of 'opencog.atomspace.Atom' objects is not writable
I guess the 'handle bar' Atom is stuck with it's amusing but unfortunate name.
Lets create our first link:
>>> n1 = a.add_node(t.ConceptNode, "I can refer to this atom now") >>> n2 = a.add_node(t.ConceptNode, "this one too") >>> l = a.add_link(t.SimilarityLink, [n1,n2]) >>> l.out [Atom(Handle(5),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(6),<opencog.atomspace.AtomSpace object at 0x203220a>)]
Up until now we've ignored Truthvalues. The TruthValue implementation is not entirely complete.Currently the bindings only support "SimpleTruthValues", since these are the most frequently used.
>>> from opencog.atomspace import TruthValue >>> alink.tv '[0.000000,0.000000=0.000000]' >>> alink.tv = TruthValue(0.5,0.1) >>> alink '[SimilarityLink <I can refer to this atom now,this one too> 0.500000 0.200000]'
Next up is queries to the AtomSpace. What if we want to iterate over all the ConceptNodes we've added so far? Most of the AtomSpace getHandleSet methods have be wrapped and also have more descriptive names for each variant (instead of the numerous overloads in the C++ API, that IMO makes things confusing).
>>> my_nodes = a.get_atoms_by_type(t.ConceptNode) >>> my_nodes [Atom(Handle(6),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(5),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(4),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(3),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(2),<opencog.atomspace.AtomSpace object at 0x203220a>), Atom(Handle(1),<opencog.atomspace.AtomSpace object at 0x203220a>)]
By default, subtypes are also retrieved, but we can exclude subtypes if we want to be specific.
>>> a.add_node(t.Node, "I am the one true Node") >>> a.get_atoms_by_type(t.Node,subtype=False) [Atom(Handle(8),<opencog.atomspace.AtomSpace object at 0x203220a>)] >>> print my_specific_nodes[0] node[Node:I am the one true Node]
There are other queries by type, outgoing set, name etc. See tests/opencog/cython/test_atomspace.py for the complete picture.
Note: You can also now use the AtomSpace.add() method which automatically determines and checks the required arguments for the type:
>>> a1 = a.add(t.Node, name="Easier this way") >>> a2 = a.add(t.Node, name=".. much easier") >>> a.add(t.Link, out=[a1,a2]) Atom(Handle(11),<opencog.atomspace.AtomSpace object at 0x203220a>)
The AtomSpace as a container
The AtomSpace supports the container methods Python expects for a container-like object:
>>> alink in a # is this atom in this atomspace True >>> h in a # works for handles too True >>> len(a) # how many atoms are in the atomspace? 8 >>> a[h] # using [] notation, returns the Atom, or IndexError exception # if handle not in AtomSpace Atom(Handle(4),<opencog.atomspace.AtomSpace object at 0x14b2918>)
I'm very interested in making this Pythonic as possible, so if you notice aberrations please email me.
MindAgents in Python
MindAgents modify the AtomSpace autonomously. Adding and removing atoms, updating TruthValues or anything else. The most important part for now is the "run" method, which gets called with the CogServer AtomSpace as a parameter (In the past, C++ MindAgents would be passed the CogServer itself, but it wasn't obvious to me why this is necessary).
>>> import opencog.cogserver >>> from opencog.atomspace import types >>> class MyMindAgent(opencog.cogserver.MindAgent): ... def run(self,atomspace): ... atomspace.add_node(types.ConceptNode, "test")
This will try, every CogServer cycle, to add the ConceptNode called "test".
Warning: Note the opencog.cogserver.MindAgent is subclassed using the full path. If you use:
>>> from opencog.cogserver import MindAgent # wrong
Then you'll get MindAgent showing up in the cogserver too. I'm not currently sure why, but supporting this is another improvement to add eventually.
To actually allow the CogServer to use this MindAgent there are several things to check:
- Place the module containing the MindAgent within one of these place:
- <your OpenCog data directory>/python
- a directory specified in the configuration parameter PYTHON_EXTENSION_DIRS,
- somewhere on your PYTHONPATH.
- Ensure you are loading the libPythonModule.so CogServer plugin.
- Either use the CogServer loadpy command to load the module (you should leave off the .py extension, e.g. "loadpy my_module") or place the module name in the configuration parameter PYTHON_PRELOAD.
Note: Yet another thing to do is allow all modules in the OpenCog Python directories to be loaded automatically and scanned for MindAgents.
CogServer Requests in Python
CogServer Requests are commands that can be issued in the shell or by other modules in C++ code. If save the follow to a file myrequest.py:
import opencog.cogserver from opencog.atomspace import types class MyRequest(opencog.cogserver.Request): def run(self,args,atomspace): # args is a list of strings atomspace.add_node(types.ConceptNode, args[0])
And you telnet to a running CogServer, you can run the request:
$ telnet localhost 17001 opencog> loadpy myrequest opencog> myrequest.MyRequest blah
Then the request will have added a ConceptNode with the name "blah"
Improving the bindings
As you can tell from the miscellaneous references in this document, there is much more that can be added to this interface between C++ and Python. Further improvements will be made by Joel in due course and as needed, but feel free to lend a hand!
See the Cython documentation, and then check out the source code in SOURCE_ROOT/opencog/cython/opencog. Note the use of Cython .pxd definition files which show how to make C++ classes and functions available to cython code.
TODO
- Support VersionHandles and contextual truth values
- Support add/remove signals (but depends on changing from boost::signals to ZeroMQ messaging first)
- It'd also be nice to somehow import types using "from opencog.atomspace.types import ConceptNode" (or use a * import)