Clojure OWL 0.2
I have been developing a library written in Clojure, that I can use for building OWL ontologies programmatically [@url:www.russet.org.uk/blog/2214] The basic idea behind this library is to give me something that looks like Manchester syntax [@url:www.w3.org/TR/owl2-manchester-syntax/] but which is none the less fully programmatic; it can be extended arbitrarily, both for general use and for one-off, single ontology specific custom code.
This has already shown its worth: for example, adding a syntax for "some and only" closure axioms was straightforward; likewise, I can now express disjoints and subclasses implicitly through bracket placement, rather than through named concepts [@url:www.russet.org.uk/blog/2275] Although in its early dates, I have added initial support for ontology design patterns --- in this case a value partition, which I think will be extended significantly in future versions. I have used this in my version of the Pizza ontology which seemed as good a demonstrator to start with as any [@url:robertdavidstevens.wordpress.com/2010/01/22/why-the-pizza-ontology-tutorial/] this also contains some custom "one-off" patterns, for building "named" pizzas. Taken together these where enough to constitute the first (unheralded) 0.1 of Clojure-OWL.
However for 0.2, I wanted one more feature that I think makes this now a usable alternative for developing ontologies. I wanted to be able to address ontologies that were built using other technologies, which were accessibly only as an OWL file. Of course, the library has always had the ability to build classes using URIs as strings; this facility means that it is possible to address another ontology. However, I wanted ontologies read from OWL files to be first-class citizens; classes and properties should be represented as lisp symbols [@url:www.russet.org.uk/blog/2254] providing a degree of safety to the system --- it is not possible to refer to a concept not previously defined, nor use a concept where a property is needed.
This turned out to be reasonably straightforward; Clojure-OWL now maps an individual ontology to a clojure namespace. Reading an ontology in from an OWL file is reasonably simple using the OWL API; finally, as a highly dynamic language, clojure can create new symbols on the fly with ease. To test this out, I needed a reasonably large and complex ontology: I choose OBI [@doi:10.1186/2041-1480-1-S1-S7] for reasons of familiarity.
The process of integrating it into Clojure-OWL starts to show the power of this approach. A basic outline of the code to achieve this is simple enough. It requires a location, prefix and an identifier. The location is generic, including a stream, so could be anything. I have include "obi.owl" as a class resource; I can use a URL, but accessing the network every time I wish to use things is a pain, although this would effective provide a form of continuous integration.
(defread obi ;; something that the OWL API can interpret. This includes a stream, so ;; it's totally generic. :location (IRI/create (clojure.java.io/resource "obi.owl")) ;; the prefix that you want to use in this case :prefix "obo" ;; normally only things from this IRI will be imported :iri "http://purl.obolibrary.org/obo/" )
On its own though, OBI contains a large number of concepts from many different ontologies. Normally, I filter for only entities whose identifier starts with the IRI above. This fails with OBO ontologies which use a sort of namespacing mechanism and a numeric identifier. So I need to apply a custom filter.
:filter (fn [e] (and (instance? OWLNamedObject e) (.startsWith (.toString (.getIRI e)) "http://purl.obolibrary.org/obo/OBI" )) )
I can think of many other uses for this sort of filtering; if I want to include a subset of entities then this would work also.
The next problem is OBIs use of semantic-free identifiers
[@url:www.russet.org.uk/blog/2040] Even if the reasons behind this
decision are good, the resulting numeric atoms (
useless --- I want to be able to say
So for this I use a custom transform function. This forms the name of
the lisp symbol from the label instead, with a regexp fix to remove
characters which are illegal --- spaces for obvious reasons, and "/"
which clojure uses as a namespace qualifier.
:transform ;; fix the space problem (fn [e] (clojure.string/replace ;; with luck these will always be literals, so we can do this ;; although not true in general (.getLiteral ;; get the value of the annotation (.getValue (first ;; filter for annotations which are labels ;; is lazy, so doesn't eval all (filter #(.. % (getProperty) (isLabel)) ;; get the annotations (.getAnnotations e (owl.owl/get-current-jontology)))))) #"[ /]" "_" ))
The final addition is to add the ability to import an ontology into the current; without this, references to another ontology will share URIs, but not pull the referenced ontology with all its axioms into the current namespace. Without this, reasoning will not work as expected. This is achieved with a single form:
Unfortunately, I have had to disable Hermit functionality --- our current mavenized version of HermiT [@url:hermit-reasoner.com] is working, but failing a few tests from incompatibilities with the current OWL API. This will be re-enabled in the new version.
Taken together, I think, clojure-owl now represents a reasonable programmatic environment for OWL. We now have the tools we need to replicate the the essential functionality of a tool like Protege; not that I am trying to replace Protege, as I still use it as a viewer for my generated ontologies. But, more over, I can now extend this functionality. As well as importing an ontology, I can filter the import so that only certain entities are available --- an ad-hoc form of privacy. In later versions, I will probably add more explicit support for this. We can now package an OWL ontology in a Jar and publish it to any Maven repository. You may love or hate maven (generally, the latter), but being able to resolve dependencies is a strong point, especially as it brings versioning with it.