Aquí está la esencia de cómo estoy haciendo las cosas y qué tipos de inferencia/modelado están habilitados.
Casi todo lo que usamos pertenece a OWL Lite con la excepción de la propiedad owl:hasValue
que usamos para definir un mapeo 1-1 entre conjuntos de etiquetas y nombres de clases. Por este motivo, la solución actual pertenece a OWL DL.
Esto también significa que necesitamos un razonador, pero afortunadamente no uno que admita OWL Full :). OWL-RL es una buena opción que funciona con RDFlib.
# G is our RDFlib Graph
# apply reasoning to expand all of the inferred triples
import owlrl
owlrl . DeductiveClosure ( owlrl . OWLRL_Semantics ). expand ( G )
# get namespaces
print ( list ( G . namespaces ()))
G . query ( "SELECT ?x WHERE { ?x brick:hasTag tag:Equip }" )
Podemos serializar la forma expandida del gráfico en el disco si necesitamos usar un procesador de consultas SPARQL que no admita el razonamiento.
https://brickschema.org/schema/1.1.0/Brick#
owl:Class
y están organizadas en una jerarquía con rdfs:subClassOf
owl:equivalentClass
skos:definition
brick:AHU_Average_Exhaust_Air_Static_Pressure_Sensor
es solo un Average_Exhaust_Air_Static_Pressure_Sensor
que es un punto de una AHU.Las clases raíz que hemos definido son:
Equipment
Location
Point
Tag
Substance
Quantity
(Las relaciones son el término Brick para las propiedades de objeto del búho entre instancias de clases)
A nivel de superficie, las relaciones funcionan igual que en el Brick original. Todavía existen las mismas relaciones (donde recordé definirlas) y tienen sus inversas definidas usando owl:inverseOf
.
Los dominios y rangos se definen en términos de clases. Afirmar que el rdfs:range
de una relación es de la clase brick:Equipment
significa que el objeto de la relación debe ser una instancia de la clase brick:Equipment
.
Este prototipo incluye subrelaciones además de relaciones. Se pueden utilizar subrelaciones en lugar de superrelación para agregar más detalles a la naturaleza de esa relación. El único ejemplo hasta ahora es que feedsAir
es una subpropiedad de feeds
.
Algo que debemos descubrir es cómo podríamos inferir la relación feedsAir
; ¿Quizás si los dos equipos de punto final tienen la etiqueta air
y una relación feeds
? Esto puede ser algo que deba especificarse explícitamente en lugar de inferirse.
https://brickschema.org/schema/1.1.0/BrickTag#
skos:definition
Esto se logra declarando una clase Brick (por ejemplo, Air_Temperature_Sensor
) como equivalente a una clase anónima, que es una owl:Restriction
que es la intersección de entidades que tienen ciertas etiquetas.
# in turtle format
brick:Temperature_Sensor a owl:Class ;
rdfs:subClassOf brick:Sensor ;
owl:equivalentClass [ owl:intersectionOf (
[ a owl:Restriction ;
owl:hasValue tag:Sensor ;
owl:onProperty brick:hasTag
]
[ a owl:Restriction ;
owl:hasValue tag:Temperature ;
owl:onProperty brick:hasTag
]
) ] .
El primer owl:Restriction
es el conjunto de todas las clases que tienen tag:Sensor
como valor para una de sus propiedades brick:hasTag
.
Esto significa que un sensor de temperatura :ts1
podría definirse de dos maneras diferentes y el razonador inferiría los otros triples:
# using classes
:ts1 a brick:Temperature_Sensor
# using tags
:ts1 brick:hasTag tag:Temp
:ts1 brick:hasTag tag:Sensor
Brick ahora define una jerarquía de sustancias ( substances.py
) y una jerarquía de cantidades ( quantities.py
). Las sustancias y cantidades pueden estar relacionadas con equipos y puntos.
No todo esto se implementa. En el prototipo actual, los sensores se relacionan con sustancias y cantidades a través de la relación brick:measures
.
:ts1 a brick:Temperature_Sensor
:ts1 brick:measures :Air
# this implies the following
:ts1 a brick:Air_Temperature_Sensor
Podemos subclasificar aún más las sustancias para proporcionar un contexto a nivel de sistema o proceso a sus definiciones:
:ts1 a brick:Sensor
:ts1 brick:measures brick:Return_Air
:ts1 brick:measures brick:Temperature
# implies...
:ts1 a brick:Return_Air_Temperature_Sensor
Brick utiliza restricciones OWL para refinar las clases en función de dichas relaciones. Para este ejemplo, debido a que :ts1
mide Aire (específicamente la clase brick:Air
), OWL infiere nuestro sensor como un brick:Air_Temperature_Sensor
.
Así es como se ve la definición en tortuga:
brick:Air_Temperature_Sensor a owl:Class ;
rdfs:subClassOf brick:Temperature_Sensor ;
owl:equivalentClass [ owl:intersectionOf ( [ a owl:Restriction ;
owl:hasValue brick:Temperature ;
owl:onProperty brick:measures ] [ a owl:Restriction ;
owl:hasValue brick:Air ;
owl:onProperty brick:measures ] ) ] .
Nota : aquí utilizamos clases como valores, lo cual es diferente al resto de Brick. Esto se llama "juego de palabras". Esto es para evitar tener que crear instancias de sustancias para que nuestros sensores midan, etc., pero se reserva la posibilidad de implementar esto en el futuro. Los casos de sustancias pueden modelar regiones/fragmentos de "materiales" en una etapa de un proceso, por ejemplo, el agua que ingresa a un enfriador o la región de aire mezclado de una unidad de tratamiento de aire.
En lugar de perdernos en la complicada cuestión de cómo formatear todo como YAML, simplemente usamos diccionarios de Python para no tener que preocuparnos por ninguna (bueno, no tanta) lógica de análisis.
definitions = {
"Lighting_System" : {
"tagvalues" : [ # Lighting_System class is equivalent to the Lighting tag
( BRICK . hasTag , TAG . Lighting ),
# if you have more required tags add them as their own tuple in the list
],
# defining subclasses. This can be nested ad-infinitum
"subclasses" : {
"Lighting" : {
"subclasses" : {
"Luminaire" : {},
"Luminaire_Driver" : {},
},
},
"Interface" : {
"subclasses" : {
"Switch" : {
"subclasses" : {
"Dimmer" : {},
},
},
"Touchpanel" : {},
},
},
},
}
}
define_subclasses ( definitions , BRICK . Equipment )
Por ahora, el código es la documentación. Mire equipment.py
, point.py
, etc. para ver ejemplos y cómo agregar a cada una de las jerarquías de clases.
Autores: Gabe Fierro