SPIN Primer: Rectangles and Squares

Editor:
Holger Knublauch, TopQuadrant, Inc.

This document introduces SPIN to people familiar with basic concepts of RDF and SPARQL. A simple model about rectangles and squares is used to introduce all major features of SPIN, including class definitions, constraints, rules, functions and templates. This document can serve as the "Hello, World" example of SPIN.


 

Introduction

SPIN - the SPARQL Inferencing Notation - is a W3C Member Submission that can be used to create rich semantic models based on SPARQL. SPIN is a light-weight RDF vocabulary that can be used to define classes together with constraints and rules that are executable with mainstream RDF technology. SPIN applies object-oriented principles such as inheritance and encapsulation, and can also be used to define high-level modeling languages with executable semantics.

This document uses a running example model illustrated in the following UML diagram. In a nutshell, there is a class Rectangle which has a subclass Square. All Rectangles can have a width and a height, both integers greater than 0. The area of a Rectangle gets computed by multiplying width and height. Squares must have equal width and height.

The Turtle source code of the complete SPIN file can be found at http://topbraid.org/examples/spinsquare.ttl. Here are the basic class and property definitions, as a simple RDF Schema or OWL ontology:

ss:Rectangle
  rdf:type rdfs:Class ;
  rdfs:label "Rectangle" .

ss:Square
  rdf:type rdfs:Class ;
  rdfs:label "Square" ;
  rdfs:subClassOf ss:Rectangle .
  
ss:area
  rdf:type rdf:Property ;
  rdfs:label "area" .

ss:height
  rdf:type rdf:Property ;
  rdfs:label "height" .
  
ss:width
  rdf:type rdf:Property ;
  rdfs:label "width" . 

Rules

SPIN makes it possible to attach executable rules to classes. Rules are represented as SPARQL CONSTRUCT queries that apply to all instances of the associated class and its subclasses. In those rules, the variable ?this refers to each instance of those classes. A SPIN execution engine will make sure that ?this has the correct values. The triples that are constructed by such a rule become "inferred" and are added to the RDF graph, so that other rules can "see" the new triples. In the following example, the value of ss:area gets computed by multiplying the values of ss:width and ss:height.

ss:Rectangle
  spin:rule [
      rdf:type sp:Construct ;
      sp:text """
        CONSTRUCT {
            ?this ss:area ?area .                 # Infer ?area as a value of ss:area
        }
        WHERE {
            ?this ss:width ?width .               # Get the width of ?this Rectangle
            ?this ss:height ?height .             # Get the height of ?this Rectangle
            BIND ((?width * ?height) AS ?area) .  # Compute area := width * height
        }
        """ ;
    ] .

The example above introduces some basic concepts of SPIN: SPARQL queries are represented as RDF resource of type sp:Construct, sp:Ask or sp:Select (depending on whether they are CONSTRUCT, ASK or SELECT queries). These query resources have a property sp:text that contains the actual SPARQL query that can be parsed for execution.

The property spin:rule is used to link a class with a rule. The values of this property must be either CONSTRUCT queries or template calls that wrap a CONSTRUCT query (see below).

Constraints

SPIN constraints are syntactically similar to rules, only that they create instances of spin:ConstraintViolation instead of arbitrary triples. In contrast to rules, constraints do not produce new inferences but can only be used to verify that certain invariants are valid. The instances of spin:ConstraintViolation are basically just a mechanism to create violation reports.

Constraints are attached to classes using the property spin:constraint, and they apply to all instances of the associated class and its subclasses. The following example expresses that Squares must have equal width and height. Instances that fail this condition are reported as constraint violation objects that also point at the instance (via spin:violationRoot) and (if available) the property that holds the illegal value (via spin:violationPath). Each constraint violation may also have a human-readable label and other properties.

ss:Square
  spin:constraint [
      rdf:type sp:Construct ;
      sp:text """
        # Width and height must be equal
        CONSTRUCT {
            _:cv a spin:ConstraintViolation ;
                spin:violationRoot ?this ;
                spin:violationPath ss:height ;
                rdfs:label "Width and height of a Square must be equal"
        } 
        WHERE {
            ?this ss:width ?width .
            ?this ss:height ?height .
            FILTER (?width != ?height) .
        }
        """ ;
    ] .

Templates

SPIN Templates are "boxed" queries that can be used as values of spin:rule or spin:constraint. The role of a template is to encapsulate a reusable piece of SPARQL logic so that users do not need to reinvent the wheel. Templates hide the complexity of the underlying SPARQL query and are therefore suitable for people who are not familiar with SPARQL. The following example declares a SPIN template that can be used for SPIN constraints to express that the values of a given property shall be greater than 0.

ss:PositivePropertyValueConstraint
  rdf:type spin:ConstructTemplate ;
  rdfs:subClassOf spin:ConstructTemplates ;
  rdfs:label "Positive property value constraint" ;
  spin:constraint [
      rdf:type spl:Argument ;
      spl:predicate arg:property ;
      spl:valueType rdf:Property ;
      rdfs:comment "The property to constrain (e.g. ss:width or ss:height)." ;
    ] ;
  spin:body [
      rdf:type sp:Construct ;
      sp:text """
        CONSTRUCT {
            _:cv a spin:ConstraintViolation ;
                 spin:violationRoot ?this ;
                 spin:violationPath ?property ;
                 rdfs:label ?label .
        }
        WHERE {
            ?this ?property ?value .
            FILTER (?value <= 0) .
            BIND (CONCAT(\"Property \", xsd:string(?property), 
                \" must only have positive values, but found \", xsd:string(?value)) AS ?label) .
        }
        """ ;
    ] ;
  spin:labelTemplate "Values of property {?property} must be > 0" .

In the example above, the template takes an argument, as specified by the spl:Argument. This argument is represented by a value of the property arg:property which is mapped to the variable ?property when the query executes. Here is an example that instantiates, or "calls", the template to define constraints on the properties ss:width and ss:height for all instances of the class ss:Rectangle.

ss:Rectangle
  spin:constraint [
      rdf:type ss:PositivePropertyValueConstraint ;
      arg:property ss:height ;
    ] ;
  spin:constraint [
      rdf:type ss:PositivePropertyValueConstraint ;
      arg:property ss:width ;
    ] .

When a SPIN engine encounters such a constraint definition, it will execute the spin:body of the template and pre-bind the declared argument variables with the values specified in the template call. In the case of ss:height, the template's body basically becomes the following, where every appearance of the variable ?property has been replaced with the constant ss:height.

CONSTRUCT {
    _:cv a spin:ConstraintViolation ;
        spin:violationRoot ?this ;
        spin:violationPath ss:height ;
        rdfs:label ?label .
}
WHERE {
    ?this ss:height ?value .
    FILTER (?value <= 0) .
    BIND (CONCAT("Property ", xsd:string(ss:height), 
              " must only have positive values, but found ", xsd:string(?value)) AS ?label) .
}

SPIN templates make it possible to create libraries of reusable constraints and rules (and other use cases), so that users do not need to learn SPARQL. The SPIN standard comes with one such reusable constraint spl:Argument that is used by SPIN to declare the arguments of templates. The spinsquare example also uses a template called spl:Attribute that can be used to declare the cardinality and value type of a certain property:

ss:Rectangle
  spin:constraint [
      rdf:type spl:Attribute ;
      spl:maxCount 1 ;
      spl:predicate ss:area ;
      spl:valueType xsd:integer ;
      rdfs:comment "The area of a Rectangle, defined as the product of width x height." ;
    ] ;
  spin:constraint [
      rdf:type spl:Attribute ;
      spl:maxCount 1 ;
      spl:predicate ss:height ;
      spl:valueType xsd:integer ;
      rdfs:comment "The height of a Rectangle." ;
    ] ;
  spin:constraint [
      rdf:type spl:Attribute ;
      spl:maxCount 1 ;
      spl:predicate ss:width ;
      spl:valueType xsd:integer ;
      rdfs:comment "The width of a Rectangle." ;
    ] .

The example above highlights that SPIN templates can be used to create higher-level modeling languages that introduce constructs such as the spl:Attribute above together with semantics that are executable by any SPIN-compliant engine. At the same time, high-level elements such as spl:Attributes can also be used by other engines that do not necessarily rely on SPARQL. For example, if a community agrees to formalize property cardinalities via spl:maxCount and spl:minCount, then user interface tools can exploit this structure to generate suitable input fields.

Functions

SPIN Functions are similar to Templates in their syntax, but they are used to declare new SPARQL functions based on an encapsulated, reusable query. The following snippet defines a function ss:computeArea that takes a Rectangle as its argument and returns an integer that is the result of multiplying the Rectangle's width with its height.

ss:computeArea
  rdf:type spin:Function ;
  rdfs:subClassOf spin:Functions ;
  rdfs:label "compute area" ;
  rdfs:comment "Computes the area of a given rectangle (?arg1) as the product of its width and height." ;
  spin:constraint [
      rdf:type spl:Argument ;
      spl:predicate sp:arg1 ;
      spl:valueType ss:Rectangle ;
      rdfs:comment "The rectangle to compute the area of." ;
    ] ;
  spin:body [
      rdf:type sp:Select ;
      sp:text """
        SELECT ((?width * ?height) AS ?result)
        WHERE {
            ?arg1 ss:width ?width .
            ?arg1 ss:height ?height .
        }
        """ ;
    ] ;
  spin:returnType xsd:integer .

In SPIN compliant SPARQL processors, this new function can be used such as in the following example:

SELECT *
WHERE {
    ?rectangle a ss:Rectangle .
    BIND (ss:computeArea(?rectangle) AS ?area) .
}

SPIN functions have a SELECT query as their body, and this query needs to have one result variable (here: ?result). The first binding of this variable will be used as result of the function execution. The execution mechanism is illustrated in the following diagram.

Since SPIN is based on Semantic Web standards, the principles of Linked Data apply. For example, SPARQL engines can look up the definition of a new function that it has not encountered before by following the URI of the function (here: ss:computeArea). Likewise, when a SPIN constraint engine encounters a resource, it can follow the declared rdf:type triples of that resource to look up the definition of the class on the internet, a company-wide local network or a custom URI resolution mechanism. The definition of the class may involve spin:constraint triples that help the SPIN engine make sense of objects in an entirely declarative manner. As a result of this, SPIN helps to turn Linked Data into Linked Semantic Objects - self-describing resources that support an extensible and dynamic system architecture. Such an architecture only requires to hard-code a few generic software components, while much of the runtime behavior is controlled and discovered dynamically. SPIN can also be used in conjunction with other semantic web languages like OWL and to extend existing ontologies with the rich expressivity of SPARQL.