BrickSchema / py-brickschema

Python package for working with Brick (brickschema.org/)
Other
55 stars 15 forks source link

Running inference .expand(..) does not add inverse relationships, also adds redundant owl:sameAs triples #46

Closed oising closed 3 years ago

oising commented 3 years ago

I noticed that the OWLRS expand adds owl:sameAs axioms for every node, targeting self?

If we take the example apartment.ttl from brick (slightly modified to add a setpoint and which has 23 asserted triples in total), and run inference, I end up with 150 triples, the majority (entirety even?) of which are :a owl:sameAs :a -- well, obviously :D

Also, none of the inverse relationships are inferred? e.g. all of the brick:isPartOf references... I would have expected inferrence to have added the brick:hasPart axioms? Isn't that the whole point of this?

apartment input:

@prefix apt: <apartment#> .
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
#@prefix qudt: <http://qudt.org/schema/qudt/> .
@prefix unit: <http://qudt.org/vocab/unit/> .

apt:bedroom a brick:Room ;
    brick:isPartOf apt:bedroom_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:kitchen a brick:Room ;
    brick:isPartOf apt:kitchen_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:living_room a brick:Room ;
    brick:isPartOf apt:living_room_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:bedroom_lighting a brick:Lighting_Zone .

apt:kitchen_lighting a brick:Lighting_Zone .

apt:living_room_lighting a brick:Lighting_Zone .

brick:Apartment rdfs:subClassOf brick:Space .

apt:my_apartment a brick:Apartment .

apt:thermostat_zone a brick:HVAC_Zone .

apt:thermostat1 a brick:Thermostat ;
    brick:isPartOf apt:thermostat_zone .

apt:setpoint1 a brick:Temperature_Setpoint ;
    brick:isPartOf apt:thermostat1 ;
    brick:hasUnit unit:DEG_F .

Inferred output:

@prefix apt: <file:///.../apartment#> .
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix unit: <http://qudt.org/vocab/unit/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

apt:bedroom a brick:Room ;
    owl:sameAs apt:bedroom ;
    brick:isPartOf apt:bedroom_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:kitchen a brick:Room ;
    owl:sameAs apt:kitchen ;
    brick:isPartOf apt:kitchen_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:living_room a brick:Room ;
    owl:sameAs apt:living_room ;
    brick:isPartOf apt:living_room_lighting,
        apt:my_apartment,
        apt:thermostat_zone .

apt:setpoint1 a brick:Temperature_Setpoint ;
    owl:sameAs apt:setpoint1 ;
    brick:hasUnit unit:DEG_F ;
    brick:isPartOf apt:thermostat1 .

rdf:HTML a rdfs:Datatype ;
    owl:sameAs rdf:HTML .

rdf:LangString a rdfs:Datatype ;
    owl:sameAs rdf:LangString .

rdf:PlainLiteral a rdfs:Datatype ;
    owl:sameAs rdf:PlainLiteral .

rdf:XMLLiteral a rdfs:Datatype ;
    owl:sameAs rdf:XMLLiteral .

rdf:type owl:sameAs rdf:type .

rdfs:Literal a rdfs:Datatype ;
    owl:sameAs rdfs:Literal .

rdfs:comment a owl:AnnotationProperty ;
    owl:sameAs rdfs:comment .

rdfs:isDefinedBy a owl:AnnotationProperty ;
    owl:sameAs rdfs:isDefinedBy .

rdfs:label a owl:AnnotationProperty ;
    owl:sameAs rdfs:label .

rdfs:seeAlso a owl:AnnotationProperty ;
    owl:sameAs rdfs:seeAlso .

rdfs:subClassOf owl:sameAs rdfs:subClassOf .

xsd:NCName a rdfs:Datatype ;
    owl:sameAs xsd:NCName .

xsd:NMTOKEN a rdfs:Datatype ;
    owl:sameAs xsd:NMTOKEN .

xsd:Name a rdfs:Datatype ;
    owl:sameAs xsd:Name .

xsd:anyURI a rdfs:Datatype ;
    owl:sameAs xsd:anyURI .

xsd:base64Binary a rdfs:Datatype ;
    owl:sameAs xsd:base64Binary .

xsd:boolean a rdfs:Datatype ;
    owl:sameAs xsd:boolean .

xsd:byte a rdfs:Datatype ;
    owl:sameAs xsd:byte .

xsd:date a rdfs:Datatype ;
    owl:sameAs xsd:date .

xsd:dateTime a rdfs:Datatype ;
    owl:sameAs xsd:dateTime .

xsd:dateTimeStamp a rdfs:Datatype ;
    owl:sameAs xsd:dateTimeStamp .

xsd:decimal a rdfs:Datatype ;
    owl:sameAs xsd:decimal .

xsd:double a rdfs:Datatype ;
    owl:sameAs xsd:double .

xsd:float a rdfs:Datatype ;
    owl:sameAs xsd:float .

xsd:hexBinary a rdfs:Datatype ;
    owl:sameAs xsd:hexBinary .

xsd:int a rdfs:Datatype ;
    owl:sameAs xsd:int .

xsd:integer a rdfs:Datatype ;
    owl:sameAs xsd:integer .

xsd:language a rdfs:Datatype ;
    owl:sameAs xsd:language .

xsd:long a rdfs:Datatype ;
    owl:sameAs xsd:long .

xsd:negativeInteger a rdfs:Datatype ;
    owl:sameAs xsd:negativeInteger .

xsd:nonNegativeInteger a rdfs:Datatype ;
    owl:sameAs xsd:nonNegativeInteger .

xsd:nonPositiveInteger a rdfs:Datatype ;
    owl:sameAs xsd:nonPositiveInteger .

xsd:normalizedString a rdfs:Datatype ;
    owl:sameAs xsd:normalizedString .

xsd:positiveInteger a rdfs:Datatype ;
    owl:sameAs xsd:positiveInteger .

xsd:short a rdfs:Datatype ;
    owl:sameAs xsd:short .

xsd:string a rdfs:Datatype ;
    owl:sameAs xsd:string .

xsd:time a rdfs:Datatype ;
    owl:sameAs xsd:time .

xsd:token a rdfs:Datatype ;
    owl:sameAs xsd:token .

xsd:unsignedByte a rdfs:Datatype ;
    owl:sameAs xsd:unsignedByte .

xsd:unsignedInt a rdfs:Datatype ;
    owl:sameAs xsd:unsignedInt .

xsd:unsignedLong a rdfs:Datatype ;
    owl:sameAs xsd:unsignedLong .

xsd:unsignedShort a rdfs:Datatype ;
    owl:sameAs xsd:unsignedShort .

owl:backwardCompatibleWith a owl:AnnotationProperty ;
    owl:sameAs owl:backwardCompatibleWith .

owl:deprecated a owl:AnnotationProperty ;
    owl:sameAs owl:deprecated .

owl:equivalentClass owl:sameAs owl:equivalentClass .

owl:incompatibleWith a owl:AnnotationProperty ;
    owl:sameAs owl:incompatibleWith .

owl:priorVersion a owl:AnnotationProperty ;
    owl:sameAs owl:priorVersion .

owl:sameAs owl:sameAs owl:sameAs .

owl:versionInfo a owl:AnnotationProperty ;
    owl:sameAs owl:versionInfo .

brick:hasUnit owl:sameAs brick:hasUnit .

brick:isPartOf owl:sameAs brick:isPartOf .

apt:bedroom_lighting a brick:Lighting_Zone ;
    owl:sameAs apt:bedroom_lighting .

apt:kitchen_lighting a brick:Lighting_Zone ;
    owl:sameAs apt:kitchen_lighting .

apt:living_room_lighting a brick:Lighting_Zone ;
    owl:sameAs apt:living_room_lighting .

apt:thermostat1 a brick:Thermostat ;
    owl:sameAs apt:thermostat1 ;
    brick:isPartOf apt:thermostat_zone .

unit:DEG_F owl:sameAs unit:DEG_F .

brick:Apartment rdfs:subClassOf brick:Space ;
    owl:sameAs brick:Apartment .

brick:HVAC_Zone owl:sameAs brick:HVAC_Zone .

brick:Temperature_Setpoint owl:sameAs brick:Temperature_Setpoint .

brick:Thermostat owl:sameAs brick:Thermostat .

owl:Class owl:sameAs owl:Class .

owl:Nothing a owl:Class ;
    rdfs:subClassOf owl:Nothing,
        owl:Thing ;
    owl:equivalentClass owl:Nothing ;
    owl:sameAs owl:Nothing .

brick:Space owl:sameAs brick:Space .

apt:my_apartment a brick:Apartment,
        brick:Space ;
    owl:sameAs apt:my_apartment .

owl:Thing a owl:Class ;
    rdfs:subClassOf owl:Thing ;
    owl:equivalentClass owl:Thing ;
    owl:sameAs owl:Thing .

brick:Lighting_Zone owl:sameAs brick:Lighting_Zone .

brick:Room owl:sameAs brick:Room .

apt:thermostat_zone a brick:HVAC_Zone ;
    owl:sameAs apt:thermostat_zone .

owl:AnnotationProperty owl:sameAs owl:AnnotationProperty .

rdfs:Datatype owl:sameAs rdfs:Datatype .
oising commented 3 years ago

Here's my python script (at first I used the python reasoner, then tried reasonable to make sure)

    g = Graph(load_brick=False)
    g.load_file("apartment.ttl")
    print(f"Asserted graph has {len(g)} triples")
    g.expand(profile="owlrl", backend="reasonable")
    print(f"Inferred graph has {len(g)} triples")

    valid, _, report = g.validate(default_brick_shapes=True)

    if not valid:
        print(report)
        exit(1)
    else:
        print("valid")

    print(g.serialize(format="turtle", destination="apartment-inferred.ttl"))
oising commented 3 years ago

Ok, so I guessed maybe the reasoner requires load_brick=True but now it performs reasoning over the entire Brick schema as well as the apartment. And I end up with garbage/anonymous types in the output:

apt:my_apartment a _:f274a078263c444a0ba67e94ad7ce2e65b4,
        _:f274a078263c444a0ba67e94ad7ce2e65b5,
        _:ub1bL14227C21,
        owl:Thing,
        brick:Apartment,
        brick:Class,
        brick:Location,
        brick:Space ;
    owl:sameAs apt:my_apartment ;
    brick:hasPart apt:bedroom,
        apt:kitchen,
        apt:living_room ;
    brick:hasTag tag:Location,
        tag:Space .

apt:thermostat1 a _:f274a078263c444a0ba67e94ad7ce2e65b10,
        _:f274a078263c444a0ba67e94ad7ce2e65b1281,
        _:ub1bL14452C21,
        _:ub1bL6865C21,
        _:ub1bL6865C60,
        owl:Thing,
        brick:Class,
        brick:Equipment,
        brick:HVAC_Equipment,
        brick:Thermostat ;
    owl:sameAs apt:thermostat1 ;
    brick:hasPart apt:setpoint1 ;
    brick:hasTag tag:Equipment,
        tag:HVAC,
        tag:Thermostat ;
    brick:isPartOf apt:thermostat_zone .

This is not as clean as I thought it would be :/

oising commented 3 years ago

But the point still remains -- why does it add redundant owl:Sameas predicates?

gtfierro commented 3 years ago

Hi @oising; sorry I missed this issue. GitHub has decided it is not delivering notifications to me anymore.

I will need to spend a little time digging into this to figure out what is going on, but can you provide some context? Which reasoner are you using: OWL-RL (default), Allegrograph (if you have docker installed), or reasonable (if you have reasonable installed)?

The anonymous types in the output are an unfortunate consequence of a couple implementation details in Brick. There are some long-term musings to reduce this output by moving some elements of Brick to SHACL rather than OWL (not likely to happen in the coming months), but perhaps some useful functionality for brickschema to provide is a filtering out of blank nodes that are leafs in the model.

The redundant owl:sameAs predicates are technically part of the OWL-RL spec. I definitely agree that the reflexive properties of OWL are noisy and redundant, but many reasoning implementations still make them explicit. I think OWL-RL has an option to disable these kinds of "axiomatic" triples (https://owl-rl.readthedocs.io/en/latest/owlrl.html) but this would require further investigation.

gtfierro commented 3 years ago

Closing for now, but let me know if any of the above issues come up again