Search code examples
rdfjenaowljena-rules

Jena Rule for Validation af a Ontology


I want to validate an ontology and throw an error if anything is incorrect.

The most validation I have to do looks like this: I have a class like this:

   <owl:Class rdf:about="&schema;ExampleClass">
        <rdfs:subClassOf rdf:resource="&schema;SuperClass"/>
        <rdfs:subClassOf>
            <owl:Restriction>
                <owl:onProperty rdf:resource="&schema;myProperty"/>
                <owl:onClass rdf:resource="&schema;OtherClass"/>
                <owl:qualifiedCardinality rdf:datatype="&xsd;nonNegativeInteger">1</owl:qualifiedCardinality>
            </owl:Restriction>
        </rdfs:subClassOf>
    </owl:Class>

(The interesting part is the 2nd subClassOf.) In Protege this means ExampleClass is subClass of myProperty exactly 1 OtherClass.

So I want to validate that there is exactly one myProperty with value: an individual of type OtherClass.

Is it possible to validate rules like this? Perfect would be if there would be a rule to do this for all classes with this modeling (and maybe with also at least 1, exactly 2, ...)

Another question is: Is there a ready closed world reasoner that is doing exactly that for me?


Solution

  • Your example doesn't depend on the utilization of a closed-world principle. It depends on the introduction of a validation rule for owl:qualifiedCardinality.

    For example, let us take the sample input file that follows:

    @prefix xsd:  <http://www.w3.org/2001/XMLSchema#>.
    @prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
    @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
    @prefix owl:  <http://www.w3.org/2002/07/owl#>.
    @prefix : <urn:x-so:ex#>.
    
    :OtherClass a owl:Class .
    :SuperClass a owl:Class .
    
    :myProperty a rdf:Property
              ; rdfs:range  :OtherClass
              .
    
    :ExampleClass rdfs:subClassOf :SuperClass
                ; rdfs:subClassOf [ a owl:Restriction
                                  ; owl:onProperty :myProperty
                                  ; owl:cardinality 1
    #                             ; owl:onClass :OtherClass
    #                             ; owl:qualifiedCardinality 1
                                  ]
                .
    
    
    :o0 a :OtherClass .
    :o1 a :OtherClass .
    
    :s0 rdf:type    :ExampleClass
      ; :myProperty :o0
      ; :myProperty :o1
      .
    

    Note the commented-out lines and the introduced axiom above them. This ontology is owl-1 compliant, so there are validation rules for it. In the following test there is no validation error, why? because we can infer that, for example, :o0 owl:sameAs :o1 which results in no contradiction.

    final Model baseModel = ModelFactory.createDefaultModel();
    try( final InputStream in = this.getClass().getResourceAsStream("/so.ttl") ){
        baseModel.read(in, null, "TTL");
    }
    final OntModel model  = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_RULE_INF, baseModel);
    
    assertTrue(model.contains(s0, myProperty, o0));
    assertTrue(model.contains(s0, myProperty, o1));
    
    final ValidityReport report = model.validate();
    assertTrue( report.isValid() );
    

    In the next example however, we demonstrate that if we introduce :o0 owl:differentFrom :o1, then we derive a contradiction:

    final Model baseModel = ModelFactory.createDefaultModel();
    try( final InputStream in = this.getClass().getResourceAsStream("/so.ttl") ){
        baseModel.read(in, null, "TTL");
    }
    final OntModel model  = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_RULE_INF, baseModel);
    model.add(o1, OWL.differentFrom, o0); // NOTE!!
    assertTrue(model.contains(s0, myProperty, o0));
    assertTrue(model.contains(s0, myProperty, o1));
    
    final ValidityReport report = model.validate();
    assertFalse( report.isValid() );
    

    Given the demonstrated scenario, I'd propose the following solutions (in order of ascending difficulty):

    Solution 1: Open-World with OWL 1 Constraints

    Express your ontology in terms of owl-1 constraints if possible, and then you can utilize the existing rule sets for validation.

    Solution 2: Open-World with OWL 2 Additions

    This is not going to be easy. Take a look at etc/owl-fb.rules in jena-core and you'll note that support of some generic owl constructs (most notably, cardinality) required the development of Jena Builtin to make the rule expression simple. I linked to another answer regarding builtins if that is the direction that you intend to go.

    The following rules come from jena-core's etc/owl-fb.rules file to describe cardinality. They are not the complete set of cardinality rules.

    [restriction5: (?C owl:onProperty ?P), (?C owl:cardinality ?X)
      -> (?C owl:equivalentClass card(?P, ?X)),
         (?C rdfs:subClassOf min(?P, ?X)),
         (?C rdfs:subClassOf max(?P, ?X)) ]
    
    [restriction4: (?C owl:onProperty ?P), (?C owl:maxCardinality ?X)
      -> (?C owl:equivalentClass max(?P, ?X)) ]
    
    [validationMaxN: (?v rb:validation on()), (?C rdfs:subClassOf max(?P, ?N)) greaterThan(?N, 1) (?P rdf:type owl:DatatypeProperty) ->
        [max2b: (?X rb:violation error('too many values', 'Too many values on max-N property (prop, class)', ?P, ?C))
              <- (?X rdf:type ?C), countLiteralValues(?X, ?P, ?M), lessThan(?N, ?M)  ] ]
    

    restriction5 simply defines cardinality in terms of min and max cardinality (min and max in this example are Functors). validationMaxN is the particular rule (for N > 1) that shows how a violation can be identified. It delegates to the CountLiteralValues builtin to identify the number of bindings that exist for the property.

    If you are willing to introduce a CountQualifiedValues Builtin, then you could define a set of rules similar to the following to introduce the new axioms:

    [restriction4: (?C owl:onProperty ?P), (?C owl:maxQualifiedCardinality ?X), (?C owl:onClass ?Y)
      -> (?C owl:equivalentClass max(?P, ?X, ?Y)) ]
    
    [validationMaxN: (?v rb:validation on()), (?C rdfs:subClassOf max(?P, ?N, ?Y)) greaterThan(?N, 1) (?P rdf:type owl:ObjectProperty) ->
        [max2b: (?X rb:violation error('too many values', 'Too many values on max-QN property (prop, class, qclass)', ?P, ?C, ?Y))
              <- (?X rdf:type ?C), countQualifiedValues(?X, ?P, ?Y, ?M), lessThan(?N, ?M)  ] ]
    

    Solution 3: Closed-World with OWL 2 Additions

    This is actually not all that different from Solution 2. You will, however, be trying to define alternative semantics for OWL constructs, which is a nontrivial problem. You can introduce a few rules for validation (take a read of etc/owl-fb.rules to get examples) that capture your particular closed-world assumptions. If you enforce that they are restricted to only operate when (?v rb:validation on()), then you can ensure that you are only assuming a closed-world when you are performing validation.

    Side Discussion

    Here is an example of a cardinality restriction expressed in owl 1. It is the same as the one in the input file above. This is expressed in TURTLE syntax and is trivial to transform to RDF/XML or any of the other valid RDF serializations.

    :ExampleClass rdfs:subClassOf :SuperClass
                ; rdfs:subClassOf [ a owl:Restriction
                                  ; owl:onProperty :myProperty
                                  ; owl:cardinality 1
                                  ]
                .
    

    This pair of restrictions is not exactly semantically equivalent to owl:qualifiedCardinality, but, if you have the ability to modify your domain model, you can often work around it.

    For example, owl:qualifiedCardinality is great to say things like :People :haveBodyPart exactly 2 :Eyes. The OWL 1 workaround could be, for example, to create a :haveEye rdfs:subPropertyOf :haveBodyPart and then say :People :haveEye exactly 2 (without qualified cardinality restriction)