Shapes and Templates#
Shapes and Templates interact in interesting ways in BuildingMOTIF. In this document, we explain the utility and function of these interactions.
Recall that a Shape (SHACL shape) is a set of conditions and constraints over RDF graphs, and a Template is a function that generates an RDF graph.
Converting Shapes to Templates#
BuildingMOTIF automatically converts shapes to templates. Evaluating the resulting template will generate a graph that validates against the shape.
When BuildingMOTIF loads a Library, it makes an attempt to find any shapes defined within it. The way this happens depends on how the library is loaded:
Loading library from directory or git repository: BuildingMOTIF searches for any RDF files in the directory (recursively) and loads them into a Shape Collection; loads any instances of
sh:NodeShape
in the union of these RDF filesLoading library from ontology file: loads all instances of
sh:NodeShape
in the provided graphc
Important
BuildingMOTIF only loads shapes which are instances of both sh:NodeShape
and owl:Class
. The assumption is that owl:Class
-ified shapes could be “instantiated”.
Each shape is “decompiled” into components from which a Template can be constructed.
The implementation of this decompilation is in the get_template_parts_from_shape
method.
BuildingMOTIF currently recognizes the following SHACL properties:
sh:property
sh:qualifiedValueShape
sh:node
sh:class
sh:targetClass
sh:datatype
sh:minCount
/sh:qualifiedMinCount
sh:maxCount
/sh:qualifiedMaxCount
BuildingMOTIF currently uses the name of the SHACL shape as the name of the generated Template.
All other parameters (i.e., nodes corresponding to sh:property
) are given invented names unless
there is a sh:name
attribute on the property shape.
Example#
Consider the following shape which has been loaded into BuildingMOTIF as part of a Library:
# myshapes.ttl
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix : <urn:example/> .
: a owl:Ontology .
:vav a sh:NodeShape, owl:Class ;
sh:targetClass brick:Terminal_Unit ;
sh:property [
sh:path brick:hasPart ;
sh:qualifiedValueShape [ sh:node :heating-coil ] ;
sh:name "hc" ;
sh:qualifiedMinCount 1 ;
] ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Supply_Air_Flow_Sensor ] ;
sh:qualifiedMinCount 1 ;
] ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Supply_Air_Temperature_Sensor ] ;
sh:name "sat" ;
sh:qualifiedMinCount 1 ;
] ;
.
:heating-coil a sh:NodeShape, owl:Class ;
sh:targetClass brick:Heating_Coil ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Position_Command ] ;
sh:name "damper_pos" ; # will be used as the parameter name
sh:qualifiedMinCount 1 ;
] ;
.
This code creates myshapes.ttl
for you in the current directory.
with open("myshapes.ttl", "w") as f:
f.write("""
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix : <urn:example/> .
: a owl:Ontology .
:vav a sh:NodeShape, owl:Class ;
sh:targetClass brick:Terminal_Unit ;
sh:property [
sh:path brick:hasPart ;
sh:qualifiedValueShape [ sh:node :heating-coil ] ;
sh:name "hc" ;
sh:qualifiedMinCount 1 ;
] ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Supply_Air_Flow_Sensor ] ;
sh:qualifiedMinCount 1 ;
] ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Supply_Air_Temperature_Sensor ] ;
sh:name "sat" ;
sh:qualifiedMinCount 1 ;
] ;
.
:heating-coil a sh:NodeShape, owl:Class ;
sh:targetClass brick:Heating_Coil ;
sh:property [
sh:path brick:hasPoint ;
sh:qualifiedValueShape [ sh:class brick:Position_Command ] ;
sh:name "damper_pos" ; # will be used as the parameter name
sh:qualifiedMinCount 1 ;
] ;
.
""")
If this was in a file myshapes.ttl
, we would load it into BuildingMOTIF as follows:
from buildingmotif import BuildingMOTIF
from buildingmotif.dataclasses import Library
# in-memory instance
bm = BuildingMOTIF("sqlite://")
# load library
brick = Library.load(ontology_graph="https://github.com/BrickSchema/Brick/releases/download/nightly/Brick.ttl")
lib = Library.load(ontology_graph="myshapes.ttl")
/opt/hostedtoolcache/Python/3.11.10/x64/lib/python3.11/site-packages/pyshacl/extras/__init__.py:46: Warning: Extra "js" is not satisfied because requirement pyduktape2 is not installed.
warn(Warning(f"Extra \"{extra_name}\" is not satisfied because requirement {req} is not installed."))
2024-11-19 16:04:20,516 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/vocab/quantitykind (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,518 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/schema/shacl/qudt (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,519 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/vocab/dimensionvector (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,520 | root | WARNING: An ontology could not resolve a dependency on https://w3id.org/rec/recimports (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,520 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/schema/facade/qudt (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,521 | root | WARNING: An ontology could not resolve a dependency on https://brickschema.org/schema/Brick/ref (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,522 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/vocab/unit (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,523 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/schema/shacl/overlay/qudt (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,523 | root | WARNING: An ontology could not resolve a dependency on http://data.ashrae.org/bacnet/2020 (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,524 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/vocab/prefix (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,525 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/vocab/sou (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:20,526 | root | WARNING: An ontology could not resolve a dependency on http://qudt.org/2.1/collection/usertest (No row was found when one was required). Check this is loaded into BuildingMOTIF
2024-11-19 16:04:23,575 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7620 in libraries []
2024-11-19 16:04:23,579 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7624 in libraries []
2024-11-19 16:04:23,583 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7622 in libraries []
2024-11-19 16:04:23,588 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7618 in libraries []
2024-11-19 16:04:23,592 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7614 in libraries []
2024-11-19 16:04:23,598 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7616 in libraries []
2024-11-19 16:04:23,604 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7698 in libraries []
2024-11-19 16:04:23,608 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7696 in libraries []
2024-11-19 16:04:23,612 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7692 in libraries []
2024-11-19 16:04:23,616 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7700 in libraries []
2024-11-19 16:04:23,620 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7694 in libraries []
2024-11-19 16:04:23,626 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7631 in libraries []
2024-11-19 16:04:23,630 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7637 in libraries []
2024-11-19 16:04:23,634 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7633 in libraries []
2024-11-19 16:04:23,638 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7639 in libraries []
2024-11-19 16:04:23,642 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7635 in libraries []
2024-11-19 16:04:23,646 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7584 in libraries []
2024-11-19 16:04:23,650 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7580 in libraries []
2024-11-19 16:04:23,654 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7586 in libraries []
2024-11-19 16:04:23,658 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7578 in libraries []
2024-11-19 16:04:23,662 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7582 in libraries []
2024-11-19 16:04:23,666 | root | WARNING: Warning: could not find dependee na0f44b58d1f64ec9b142db5c03685c16b7576 in libraries []
Once the library has been loaded, all of the shapes have been turned into templates. We can load the template by name (using its full URI from the shape) as if it were defined explicitly:
# reading the template out by name
template = lib.get_template_by_name("urn:example/vav")
# dump the body of the template
print(template.body.serialize())
@prefix brick: <https://brickschema.org/schema/Brick#> .
<urn:___param___#name> a brick:Terminal_Unit,
<urn:example/vav> ;
brick:hasPart <urn:___param___#hc0> ;
brick:hasPoint <urn:___param___#p13>,
<urn:___param___#sat0> .
<urn:___param___#hc0> a <urn:example/heating-coil> .
As with other templates, we often want to inline all dependencies to get a sense of what metadata will be added to the graph.
# reading the template out by name
template = lib.get_template_by_name("urn:example/vav").inline_dependencies()
# dump the body of the template
print(template.body.serialize())
@prefix brick: <https://brickschema.org/schema/Brick#> .
<urn:___param___#name> a brick:Terminal_Unit,
<urn:example/vav> ;
brick:hasPart <urn:___param___#hc0> ;
brick:hasPoint <urn:___param___#p13>,
<urn:___param___#sat0> .
<urn:___param___#hc0> a brick:Heating_Coil,
<urn:example/heating-coil> ;
brick:hasPoint <urn:___param___#hc0-damper_pos0> .
Observe that the generated template uses the sh:name
property of each property shape to inform the paramter name. If this is not provided (e.g. for the brick:Supply_Air_Flow_Sensor
property shape), then a generated parameter will be used.