That’s right, I’ve gotten the obligatory Hy/Hi pun out of the way, right in the title. If you’re not familiar with one or the other or both of these, I’ll sum them up briefly.

Hy is a dialect of Lisp that is essentially Python under the hood. It is fully interoperable with Python, giving you the power of Python’s ecosystem with the fun of parentheses! It is more or less to Python what Clojure is to Java. I have grossly oversimplified this, but that should give you a rough idea. It is under active development and, as of this writing, they are approaching a version 1 release. Check out that link to see their GitHub repository and to learn more.

OWL, or Web Ontology Language (talk about forced acronyms!) is a means of representing knowledge on the web. That hardly begins to scratch the surface. It employs XML and RDF (Resource Description Framework) for representing knowledge and description logics for inferring new knowledge. It’s part of the broader world of the Semantic Web.

Building An Ontology

In what follows, I want to give a brief overview of building an OWL ontology programmatically, using Hy and the Python library Owlready2, written by Lamy Jean-Baptiste. As a side note, I would recommend his book about this, titled Ontologies with Python. I found it rather handy in getting me up to speed.

Representing the Knowledge of Hitting Things

Over the course of this summer now past, I’ve been spending a lot of time researching drum heads and cymbals in an attempt to make an informed decision with regards to what I wanted to replace these parts on my drum set with. This coincided with me giving a lot of thought to ontologies and knowledge graphs and how I might Itbuild them. As it happens, the constellation of drum head and cymbal types makes for a rather good domain to model in OWL, so I’ve done just that.

Aside from being personally interesting to me, I feel this was a good domain to work with because of the variety of hierarchical as well as compositional relationships. Also, who knows, it could serve as part of a handy decision-making system some day.

Getting Down to Business (and Macros)

So…the first thing that I wanted to do with this program was to establish a means of easily and conveniently specifying new entities for my ontology. These entities represent the “things” that the ontology is about. In our case here, this includes base classes for drum heads and cymbals, as well as types of cymbals, and various aspects of both drum heads and cymbals, such as materials, surfaces, etc.

With Owlready2, you simply define each entity as a class, which you can then set attributes and define equivalency relations and whatnot. You can also use these class/entity names when specifying the domains and ranges of various properties.

Of course, that’s all super cool, but there was a bit of me concerned that some of these entity definitions could get a bit messy and a bit difficult to follow, especially if this ontology got large. I also wanted to make clear in the code where I was specifying an entity definition and not any other kind of class. How to do that? Well, Hy, like any good Lisp, features a sophisticated macro system, which allows to create new syntax. With the defmacro macro (much of the language itself is defined as macros), you can specify a transformation that will take parameters and turn them into maenaingful code prior to runtime. These parameters can include placeholders for lines of code, which in this case are passed as data to be transformed into code. Gotta love that homoiconicity!

In short, I’ve taken advantage of this functionality to define my own new bit of syntax explicitly for defining OWL entities in Owlready2. I won’t go into the code in too much detail. For more information, I recommend checking out the Hy documentation.

(defn get-default [dictionary key [default None]]
    "Like `get`, but with a default if key is not present."
    (try
      (get dictionary key)
      (except [KeyError]
        default)))

;; Macro for defining entities with all options in one command
(defmacro defentity [ent-name parent [attributes {}]]
  (import hy.models [Symbol])
  `(defclass ~(Symbol ent-name) [~(Symbol parent)]
     (setv equivalent-to (get-default ~attributes ':equivalent-to None)
           label (get-default ~attributes ':label [])
           comment (get-default ~attributes ':comments [])
           seeAlso (get-default ~attributes ':see-also [])
           versionInfo (get-default ~attributes ':version-info [])
           deprecated (get-default ~attributes ':is-deprecated [])
           incompatibleWith (get-default ~attributes ':incompatible-with [])
           backwardCompatibleWith (get-default ~attributes ':backward-compatible-with [])
           isDefinedBy (get-default ~attributes ':is-defined-by []))))

As you can see in this code snippet, I first define a helper function that the macro takes advantage of. This function just returns a default value in the event that a particular dictionary key is not present. Basically, I’m just taking the standard class definition macro and inserting the entity name and its parent as Hy Symbols, which are the datatype expected at those points in the class definition. Similarly, the defentity after the defmacro symbol at the beginning of this list is itself a symbol.

Everything after the backtick (`) is not evaluated at runtime, but rather is the portion that will be transformed prior to runtime. The tildes (~) specify expressions to be evaluated at this time, in this case, transforming the ent-name and parent (passed as strings) into Symbols, as well as inserting the attributes dictionary in those get-default expressions. That first item, equivalent-to, specifies a clause or series of clauses using other entities and properties, as well as logical connectives, to specify an expression that is the same as that class. This could be handy later when running the reasoner, where certain objects matching those criteria might be found to be equivalent to certain entities and be identified as such by the reasoner. The remainder of the items are different annotations supported by OWL. These attributes do not represent an exhaustive list of the things that can be accomplished when defining entities, but they work for our purposes here.

Before proceeding further, I would like to note that the full code (including the generated .owl file) is available here.

Actually Defining the Entity

So now that that boilerplate is set up, we can see some of this stuff in action. Below is a simple entity definition using the defentity macro.

(defentity "Drumhead" "Thing"
    {:comments ["Generic drumhead class"]
     :version-info ["0.1"]})

Nothing too wild going on here. Just creating an entity called “Drumhead”, with a comment and some basic version info. Annotations in Owlready2 are specified as lists. There can be more than one item in the list for each of these. “Thing” is OWL’s top-level entity that entities with no user-defined parents inherit from.

The above form in Hy, when expanded (the transformation of a macro with parameters into runnable code), would look something like the following:

(defclass Drumhead [Thing]
   (setv equivalent-to (get-default {:comments ["Generic drumhead class"]
                                     :version-info ["0.1"]} ':equivalent-to None)
         label (get-default {:comments ["Generic drumhead class"]
                             :version-info ["0.1"]} ':label [])
         comment (get-default {:comments ["Generic drumhead class"]
                               :version-info ["0.1"]} ':comments [])
         seeAlso (get-default {:comments ["Generic drumhead class"]
                               :version-info ["0.1"]} ':see-also [])
         versionInfo (get-default {:comments ["Generic drumhead class"]
                                   :version-info ["0.1"]} ':version-info [])
         deprecated (get-default {:comments ["Generic drumhead class"]
                                  :version-info ["0.1"]} ':is-deprecated [])
         incompatibleWith (get-default {:comments ["Generic drumhead class"]
                                        :version-info ["0.1"]} ':incompatible-with [])
         backwardCompatibleWith (get-default {:comments ["Generic drumhead class"]
                                              :version-info ["0.1"]}
                                              ':backward-compatible-with [])
         isDefinedBy (get-default {:comments ["Generic drumhead class"]
                                   :version-info ["0.1"]} ':is-defined-by []))))

It’s a bit cumbersome to be sure. I’ll be able to clean this up a bit more once Hy macros support unpack mapping (** operator in Python). The shorthand syntax of the macro is quite handy here, all the same. We wouldn’t have to write all of this out for each entity, just the parts that we’re using, but defining the macro as I have allows it to be general purpose.

Ontology Design Decisions

That’s enough about the wonders of Hy and macros for now. Let’s take a second to discuss design decisions regarding the ontology itself. Basically, any noun connected to these items that I could think of became an entity. This includes the kind of drum head surface (Surface), the number of plies it has (Plies, which itself is involved in a number of thickness-related properties), etc. It’s the same with cymbals.

While many of these could have been specified as properties of the entities, I opted for entities to better demonstrate and enrich relationships in the ontology. Indeed, with some of these items, using equivalent-to relationships and other means, the reasoner might be better able to make meaningful inferences. In short, it facilitates knowledge discovery in the ontology.

Checking Things Out in Protégé

Protégé is an ontology editor developed at Stanford University’s Center for Biomedical Informatics Research. It can be used directly for editing OWL ontologies, or, as I’ve used it here, for checking what the code has produced. Aside from getting a better visualization of your ontology, this can be a handy debugging tool as well.

Check out the images from Protégé below and compare them against the code.

Classes

Data Properties

Object Properties

The Reasoner

Yeah, that thing. I’ve mentioned the reasoner a number of times in this article, but haven’t gotten into any more details about what it does or why. That ends here. “The reasoner” is actually a collection of reasoners, only one of which can be run at any time. The default in Owlready2 is HermiT. The Pellet reasoner is also available. Different reasoners offer different advantages and disadvantages.

What the reasoner does is to take the ontology as defined and make inferences about it. These inferences can take many forms, including making explicit relationships that were not defined, but are implied by how the ontology is set up, Re-parenting entities under other entities if they’ve been determined to be a subtype of that entity, classifying individuals (see OWL docs) as certain types of entities based on properties they possess, and more.

The reason the reasoner can do this is because OWL, or rather, the family of OWL languages (see section 1.1 in the linked OWL documentation above) are similar to description logics that lend themselves to automated reasoning. It’s Good Ol’-Fashioned AI (GOFAI)!

We can get a quick look at what the HermiT reasoner gives us for this ontology. It’s not very large or complex, so probably not a lot.

Inferences

Indeed, there isn’t much to see here in the logs, except to show a couple of the head test instances defined in the code are getting reclassified as thick heads and CrashRide cymbals are being appropriately classed as both Crash and Ride cymbals. In the test code, I asked the reasoner to also infer things about the property values. In this case, that would be things like what kind of surface does a head have or whether or not a cymbal has a certain kind of lathing. These things do not come out in the logs, but the inferences are made all the same. You can see this in the example code above, where I can look for certain properties, like what cymbal is manufactured by the Wuhan company, or what heads are thick heads. All of these kinds of properties can be accessed by simple function calls or attribute retrieval.

You may ask though, what is the deal with wuhan1 in that example? What is that? Allow me to explain.

(setv meinl-byzance-dark-ride (Cymbal
                                "Byzance_Dark_Ride"
                                :is-made-of (B20)
                                :has-weight "Medium_Thin"
                                :is-lathed [False]
                                :has-manufacturer (Meinl))
      wuhan-large-china (Cymbal
                          "20_inch_China"
                          :is-made-of (B20)
                          :has-weight "Thin"
                          :is-lathed [True]
                          :has-manufacturer (Wuhan)))

In the code snippet above, which is in the example code, you can see that I define a couple of different Cymbal instances. In the example in the previous image, You can see that the wuhan-large-china is the instance that we’re referring to. Note that, in this definition, the :is-made-of and :has-manufacturer properties are both instantiating instances of the B20 and Wuhan objects as well! Owlready2 automatically labels these instances according to their class name (B20 and Wuhan, respectively) in lower case, and appends a number to the end in the order that they were created. In that case, that leaves us with b201 and wuhan1. It’s the instances that we need to refer to when retrieving attributes. If we’re looking for the instances of a class, as with ThickHead in the previous image, then we simply invoke the instances() class method.

Further Work

The ontology presented here is a very simple one. My goal here was to simply get accustomed to using the tools for developing an ontology programmatically. I’m hoping that this might help you, as well.

There is so much more that can be added here that I’ve barely begun to touch on, and so many more things that I haven’t. From the Hy side, there is room for more macros, perhaps, that could help us to tighten up the syntax and reuse in other ontologies. The last code example where Cymbal instances are defined is one possibility. From the ontology standpoint, there are far more properties and entities that might be added and linking to a database with instances. There are also SWRL rules, which I haven’t touched on at all here, that can be added to enrich the ontology with further semantic information.

Fine, you may say, but what is this all good for? Naturally, only your imagination will limit the possibilities for ontology development and ontology-driven applications. I won’t go on to begin listing the innumerable existing ontologies, such as the Gene Ontology. I’ll leave that to your web searching skills. In this particular instance, this could serve as the start of an ontology to back a decision support system for drummers looking to pick out new drum heads and cymbals to fit their needs. Coupled with more instances of drum heads and cymbals and accessed through a web app or some such interface, such a thing could potentially prove a valuable tool to drummers.

I look forward to further experimenting with ontologies and posting here with the occasional interesting thing. Stay tuned!