Skip to content

Add ability to extract shape objects from a graph by their URI #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions buildingmotif/shape_builder/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,39 @@ def XONE(*nodes: Node) -> Shape:
:type nodes: Union[List[Node], Tuple[Node, ...]]
"""
return Shape().XONE(*nodes)


def shape_from_graph(graph: Graph, shape_uri: URIRef, depth: int = 20) -> "Shape":
"""Extract Shape from Graph by URIRef.
This method extracts the shape and all associated shapes into a Shape object which maintains most context needed to
run the shape in isolation.

Returns subgraph of "graph" containing triples relevant to the shape.

Algorithm:
1. Create empty shape with uri of shape_uri
2. Add cbd of shape_uri to empty shape
3. For each object in cbd check if it is of type NodeShape or PropertyShape
4. If object is a shape call this function on it and add it to this shape

:param graph: graph from which to extract shape
:type graph: Graph
:param shape_uri: URIRef of shape to extract
:type shape_uri: URIRef
:param depth: maximum recursive depth
:type depth: int"""
shape = Shape(shape_uri)
if depth < 0:
return shape
shape += graph.cbd(shape_uri)
triples = shape.triples((None, None, None))

def is_node_shape(uri: URIRef) -> bool:
types = [type for _, _, type in graph.triples((uri, A, None))]
return SH["NodeShape"] in types or SH["PropertyShape"] in types

for _, _, o in triples:
if is_node_shape(o):
shape += shape_from_graph(graph, o, depth - 1)

return shape
153 changes: 106 additions & 47 deletions notebooks/Shape_Builder.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
"metadata": {},
"outputs": [],
"source": [
"from buildingmotif.shape_builder.shape import Shape, NodeShape, PropertyShape, OR, AND, NOT, XONE\n",
"from buildingmotif.shape_builder.shape import Shape, NodeShape, PropertyShape, OR, AND, NOT, XONE, shape_from_graph\n",
"from buildingmotif.namespaces import BRICK, SH, bind_prefixes\n",
"from rdflib.namespace import Namespace\n",
"from rdflib import Graph, URIRef\n",
"from rdflib import Graph, URIRef, BNode\n",
"from rdflib.collection import Collection\n",
"BLDG = Namespace(\"urn:building\")\n",
"SHAPES = Namespace(\"urn:shapes#\")"
"SHAPES = Namespace(\"urn:shapes#\")\n"
]
},
{
Expand All @@ -23,57 +24,62 @@
"name": "stdout",
"output_type": "stream",
"text": [
"@prefix brick: <https://brickschema.org/schema/Brick#> .\n",
"@prefix ns1: <http://www.w3.org/ns/shacl#> .\n",
"@prefix ns2: <https://nrel.gov/BuildingMOTIF/constraints#> .\n",
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n",
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
"\n",
"<urn:shapes#occupancy-sensor> a sh:NodeShape ;\n",
" sh:or ( <urn:shapes#ocupancy-sensor-1> <urn:shapes#occupancy-sensor-2> ) .\n",
"<urn:shapes#occupancy-sensor> a ns1:NodeShape ;\n",
" ns1:or ( <urn:shapes#ocupancy-sensor-1> <urn:shapes#occupancy-sensor-2> ) .\n",
"\n",
"<urn:shapes#window-switch> a sh:NodeShape ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:minCount 0 ;\n",
" sh:node <urn:shapes#zone-with-room> ;\n",
" sh:path [ sh:path-one-or-more brick:feeds ] ] .\n",
"<urn:shapes#window-switch> a ns1:NodeShape ;\n",
" ns1:property [ a ns1:PropertyShape ;\n",
" ns1:minCount 0 ;\n",
" ns1:node <urn:shapes#zone-with-room> ;\n",
" ns1:path [ ns1:path-one-or-more <https://brickschema.org/schema/Brick#feeds> ] ] .\n",
"\n",
"<urn:shapes#occupancy-sensor-2> a sh:PropertyShape ;\n",
" sh:minCount 0 ;\n",
" sh:node [ sh:or ( <urn:shapes#zone-with-occ-sensor> <urn:shapes#room-with-occ-sensor> ) ] ;\n",
" sh:path [ sh:path-one-or-more brick:feeds ],\n",
" [ sh:path-zero-or-one brick:hasPart ] .\n",
"<urn:shapes#occupancy-sensor-2> a ns1:PropertyShape ;\n",
" ns1:minCount 0 ;\n",
" ns1:node [ ns1:or ( <urn:shapes#zone-with-occ-sensor> <urn:shapes#room-with-occ-sensor> ) ] ;\n",
" ns1:path [ ns1:path-zero-or-one <https://brickschema.org/schema/Brick#hasPart> ],\n",
" [ ns1:path-one-or-more <https://brickschema.org/schema/Brick#feeds> ] .\n",
"\n",
"<urn:shapes#ocupancy-sensor-1> a sh:PropertyShape ;\n",
" sh:class brick:Occcupancy_Sensor ;\n",
" sh:minCount 0 ;\n",
" sh:path brick:hasPoint .\n",
"<urn:shapes#ocupancy-sensor-1> a ns1:PropertyShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Occcupancy_Sensor> ;\n",
" ns1:minCount 0 ;\n",
" ns1:path <https://brickschema.org/schema/Brick#hasPoint> .\n",
"\n",
"<urn:shapes#room-with-occ-sensor> a sh:NodeShape ;\n",
" sh:class brick:Room ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:class brick:Occupancy_Sensor ;\n",
" sh:minCount 1 ;\n",
" sh:path brick:hasPoint ] .\n",
"<urn:shapes#room-with-occ-sensor> a ns1:NodeShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Room> ;\n",
" ns1:property [ a ns1:PropertyShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Occupancy_Sensor> ;\n",
" ns1:minCount 1 ;\n",
" ns1:path <https://brickschema.org/schema/Brick#hasPoint> ] ;\n",
" ns2:class <https://brickschema.org/schema/Brick#Room> .\n",
"\n",
"<urn:shapes#zone-with-occ-sensor> a sh:NodeShape ;\n",
" sh:class brick:HVAC_Zone ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:class brick:Occupancy_Sensor ;\n",
" sh:minCount 1 ;\n",
" sh:path brick:hasPoint ] .\n",
"<urn:shapes#zone-with-occ-sensor> a ns1:NodeShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#HVAC_Zone> ;\n",
" ns1:property [ a ns1:PropertyShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Occupancy_Sensor> ;\n",
" ns1:minCount 1 ;\n",
" ns1:path <https://brickschema.org/schema/Brick#hasPoint> ] ;\n",
" ns2:class <https://brickschema.org/schema/Brick#HVAC_Zone> .\n",
"\n",
"<urn:shapes#zone-with-room> a sh:NodeShape ;\n",
" sh:class brick:HVAC_Zone ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:minCount 1 ;\n",
" sh:node [ a sh:NodeShape ;\n",
" sh:class brick:Room ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:minCount 1 ;\n",
" sh:node [ a sh:NodeShape ;\n",
" sh:class brick:Open_Close_Status ] ;\n",
" sh:path brick:hasPoint ] ] ;\n",
" sh:path brick:hasPart ] .\n",
"<urn:shapes#zone-with-room> a ns1:NodeShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#HVAC_Zone> ;\n",
" ns1:property [ a ns1:PropertyShape ;\n",
" ns1:minCount 1 ;\n",
" ns1:node [ a ns1:NodeShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Room> ;\n",
" ns1:property [ a ns1:PropertyShape ;\n",
" ns1:minCount 1 ;\n",
" ns1:node [ a ns1:NodeShape ;\n",
" ns1:class <https://brickschema.org/schema/Brick#Open_Close_Status> ;\n",
" ns2:class <https://brickschema.org/schema/Brick#Open_Close_Status> ] ;\n",
" ns1:path <https://brickschema.org/schema/Brick#hasPoint> ] ;\n",
" ns2:class <https://brickschema.org/schema/Brick#Room> ] ;\n",
" ns1:path <https://brickschema.org/schema/Brick#hasPart> ] ;\n",
" ns2:class <https://brickschema.org/schema/Brick#HVAC_Zone> .\n",
"\n",
"\n"
]
Expand Down Expand Up @@ -158,7 +164,60 @@
}
],
"source": [
"print(NodeShape(URIRef(\"http://example.org/shapes#shape\"), \"hello\").serialize())"
"print(NodeShape(URIRef(\"http://example.org/shapes#shape\"), \"hello\").serialize())\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"@prefix brick: <https://brickschema.org/schema/Brick#> .\n",
"@prefix constraint: <https://nrel.gov/BuildingMOTIF/constraints#> .\n",
"@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n",
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n",
"@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n",
"\n",
"<urn:shapes#occupancy-sensor> a sh:NodeShape ;\n",
" sh:or ( <urn:shapes#ocupancy-sensor-1> <urn:shapes#occupancy-sensor-2> ) .\n",
"\n",
"<urn:shapes#occupancy-sensor-2> a sh:PropertyShape ;\n",
" sh:minCount 0 ;\n",
" sh:node [ sh:or ( <urn:shapes#zone-with-occ-sensor> <urn:shapes#room-with-occ-sensor> ) ] ;\n",
" sh:path [ sh:path-zero-or-one brick:hasPart ],\n",
" [ sh:path-one-or-more brick:feeds ] .\n",
"\n",
"<urn:shapes#ocupancy-sensor-1> a sh:PropertyShape ;\n",
" sh:class brick:Occcupancy_Sensor ;\n",
" sh:minCount 0 ;\n",
" sh:path brick:hasPoint .\n",
"\n",
"<urn:shapes#room-with-occ-sensor> a sh:NodeShape ;\n",
" sh:class brick:Room ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:class brick:Occupancy_Sensor ;\n",
" sh:minCount 1 ;\n",
" sh:path brick:hasPoint ] ;\n",
" constraint:class brick:Room .\n",
"\n",
"<urn:shapes#zone-with-occ-sensor> a sh:NodeShape ;\n",
" sh:class brick:HVAC_Zone ;\n",
" sh:property [ a sh:PropertyShape ;\n",
" sh:class brick:Occupancy_Sensor ;\n",
" sh:minCount 1 ;\n",
" sh:path brick:hasPoint ] ;\n",
" constraint:class brick:HVAC_Zone .\n",
"\n",
"\n"
]
}
],
"source": [
"print(shape_from_graph(shapes, SHAPES[\"occupancy-sensor\"]).serialize(format=\"ttl\"))\n"
]
},
{
Expand All @@ -185,7 +244,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.1"
"version": "3.10.12"
},
"orig_nbformat": 4,
"vscode": {
Expand Down