Support for Axiom Annotations
Since the early development of Tawny-OWL and easy to use syntax has been a specific objective (n.d.) as well as hiding some of the complexity of the OWL API. The intension has always been for Tawny-OWL to be an ontology developer tool first and a programmatic library second and keeping this in mind has been part of the reason that I believe does fill these objectives.
Unfortunately, the other part of the reason is that Tawny-OWL does hides functionality that is available in the OWL API. Or, more strictly, does not uncover it. Tawny-OWL is implemented in Clojure and what is possible in Java is also possible in Java.
One of the key decisions was to hide the support that the OWL API provides for certain forms of annotation, in particular annotatons on axioms. Of course, Tawny-OWL allows you to add annotations to entities. This is used to enable labels and comments on any entity. But axiom annotations allow the description of the relationships between entities. So, for example, as well as attaching comments on two classes, it is also possible to attach a comment on the sub/superclass relatinship between the two.
The main reason that Tawny-OWL did not support these natively is that it takes an entity-centric view of OWL. So, if we consider this statement:
(defclass A
:super B
:label "A")
We are describing the entity A
primarily. In fact, this statement
translates into two axioms, which we can see in the OWL/XML
representation which looks like this:
<SubClassOf>
<Class IRI="#A"/>
<Class IRI="#B"/>
</SubClassOf>
<AnnotationAssertion>
<AnnotationProperty abbreviatedIRI="rdfs:label"/>
<IRI>#A</IRI>
<Literal xml:lang="en"
datatypeIRI="http://www.w3.org/1999/02/22-rdf-syntax-ns#PlainLiteral">A</Literal>
</AnnotationAssertion>
The defclass
statement above returns the entity (actually, the var as
it is a def form, but the var contains the entity), rather than the
axioms. If I wished to return the axioms, as there are several, I would
need a list, or more probably, a data structure so that I could extract
the axiom I wanted. This would, however, complicate life considerably.
For instance, B
would now refer to this data structure, which would
need unpicking for its use here. Worse, the OWL API works by mutation,
so the axioms in B
might now reflect only some of the axioms refering
to B
.
Of course, there is a way around this, which is to dip down into the OWL API, fetching the axioms this way. As far as I can tell, annotations need to be added at the time the axiom is created (it is probably possible to do it later as well). This example comes from my recasting of the OWL Primer ontology.
(add-axiom
(.getOWLSubClassOfAxiom (owl-data-factory)
Man Person #{(owl-comment "States that every man is a person")}))
This works well, but the syntax is not nice, we need to do a direct call
to the OWL API. We are not even using the add-subclass
function. This
did not bother my overly, as it was not something that I thought would
be needed often.
Unfortunately, it is something that the Gene Ontology people do often, including, for instance, annotating labels with the source of knowledge for these labels. If I am to support them, I need an attractive syntax that fits with current Tawny-OWL syntax. After a couple of attempts, I decided on this:
(defclass A
:super (annotate B
(owl-comment "A is a kind of B")))
The axioms in Tawny-OWL are syntactically implicit, describing the
:super
relationship between A
, so I cannot directly address these.
But attaching an annotation to B
in this way is unambiguous. Compare
these two statements that it might otherwise be mistaken for; in one
case, we annotate A
with a comment (which is most common thing to do)
or, we inline an annotation of B
(which would probably be better not
inline!).
(defclass A
:super B
:annotation (owl-comment "A is an interesting entity"))
(defclass A
:super (owl-class B
:annotation
(owl-comment "B is an interesting entity")))
This also extends naturally to other axioms, including annotation labels.
(defclass A
:annotation (annotate (label "A")
(owl-comment "According to me")))
The implementation of this took me several attempts, including some
fairly painful and ultimately unsuccesful macros. In the end, I found a
much simpler solution. annotate
returns a clojure record which
contains both the entity — (label "A")
or B
in these examples — and
the annotation always an owl-comment
here, but potentially anything.
This record is passed through the Tawny-OWL function call stacks in
place of the raw entity, until the appropriate axiom is created. I then
unpick this object with two calls to protocol methods — as-entity
and
as-annotations
like so.
(.getOWLAnnotationAssertionAxiom
(owl-data-factory)
(as-iri named-entity)
^OWLAnnotation (as-entity annotation)
(as-annotations annotation)
The protocol implementations are trivial.
(defrecord Annotated [entity annotations]
Entityable
(as-entity [this] entity)
Annotatable
(as-annotations [this] annotations))
These allow me to avoid checking for an Annotated
object when creating
my axiom. In most cases, I will not have one of these, but a normal
OWLObject
. Or a Long
, String
or even a Keyword
for property
characteristics. So I extend the protocols to cover these cases also,
with even more trivial implementations.
(extend-type
Object
Entityable
(as-entity [entity] entity))
(extend-type
Object
Annotatable
(as-annotations [entity]
#{}))
Finally, the annotate
function broadcasts as do many other functions
in Tawny-OWL, so it is possible to annotate several axioms at once. So,
for example, here explicitly using a list.
(defclass A
:super (annotate [B C D]
(owl-comment "All of these are supers")))
Or, implicitly with a function existential restriction that itself uses broadcasting.
(defclass A
:super (annotate (owl-some r B C D)
(owl-comment "All of these are existentials")))
While the syntax is slightly more complex than most of Tawny-OWL, it is a considerable improvement on dropping down to the OWL API layer beneath; and, ultimately, this form of annotation is a more complex usage of OWL.