Skip to content

Commit

Permalink
Group diffset (#266)
Browse files Browse the repository at this point in the history
* Group diffset

* Try to fix test

* Add mode Dict
  • Loading branch information
haneslinger authored Aug 30, 2023
1 parent d4c2b6f commit 87b37fb
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 251 deletions.
5 changes: 4 additions & 1 deletion buildingmotif/api/views/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,8 @@ def validate_model(models_id: int) -> flask.Response:
return {
"message": vaildation_context.report_string,
"valid": vaildation_context.valid,
"reasons": [x.reason() for x in vaildation_context.diffset],
"reasons": {
focus_node: [gd.reason() for gd in grahdiffs]
for focus_node, grahdiffs in vaildation_context.diffset.items()
},
}, status.HTTP_200_OK
45 changes: 20 additions & 25 deletions buildingmotif/dataclasses/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ class ValidationContext:
model: "Model"

@cached_property
def diffset(self) -> Set[GraphDiff]:
def diffset(self) -> Dict[Optional[URIRef], Set[GraphDiff]]:
"""The unordered set of GraphDiffs produced from interpreting the input
SHACL validation report.
"""
Expand All @@ -277,7 +277,7 @@ def as_templates(self) -> List["Template"]:
"""
return diffset_to_templates(self.diffset)

def _report_to_diffset(self) -> Set[GraphDiff]:
def _report_to_diffset(self) -> Dict[Optional[URIRef], Set[GraphDiff]]:
"""Interpret a SHACL validation report and say what is missing.
:return: a set of GraphDiffs that each abstract a SHACL shape violation
Expand All @@ -289,7 +289,7 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
# proppath = SH["property"] | (SH.qualifiedValueShape / SH["property"]) # type: ignore

g = self.report + self._context
diffs: Set[GraphDiff] = set()
diffs: Dict[Optional[URIRef], Set[GraphDiff]] = defaultdict(set)
for result in g.objects(predicate=SH.result):
# check if the failure is due to our count constraint component
focus = g.value(result, SH.focusNode)
Expand All @@ -308,13 +308,9 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
# here, our 'self.focus' is the graph itself, which we don't want to have bound
# to the templates during evaluation (for this specific kind of diff).
# For this reason we override focus to be None
diffs.add(
diffs[None].add(
GraphClassCardinality(
None,
validation_report,
g,
of_class,
int(expected_count),
None, validation_report, g, of_class, int(expected_count)
)
)
elif (
Expand All @@ -323,7 +319,9 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
):
requiring_shape = g.value(result, SH.sourceShape)
expected_class = g.value(requiring_shape, SH["class"])
diffs.add(RequiredClass(focus, validation_report, g, expected_class))
diffs[focus].add(
RequiredClass(focus, validation_report, g, expected_class)
)
elif (
g.value(result, SH.sourceConstraintComponent)
== SH.NodeConstraintComponent
Expand Down Expand Up @@ -352,7 +350,7 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
# extra = g.value(result, SH.sourceShape / proppath) # type: ignore

if focus and (min_count or max_count) and classname:
diffs.add(
diffs[focus].add(
PathClassCount(
focus,
validation_report,
Expand All @@ -367,7 +365,7 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
shapename = g.value(result, SH.sourceShape / shapepath) # type: ignore
if focus and (min_count or max_count) and shapename:
extra_body, deps = get_template_parts_from_shape(shapename, g)
diffs.add(
diffs[focus].add(
PathShapeCount(
focus,
validation_report,
Expand All @@ -382,7 +380,7 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
)
continue
if focus and (min_count or max_count):
diffs.add(
diffs[focus].add(
RequiredPath(
focus,
validation_report,
Expand All @@ -395,7 +393,9 @@ def _report_to_diffset(self) -> Set[GraphDiff]:
return diffs


def diffset_to_templates(diffset: Set[GraphDiff]) -> List["Template"]:
def diffset_to_templates(
grouped_diffset: Dict[Optional[URIRef], Set[GraphDiff]]
) -> List["Template"]:
"""Combine GraphDiff by focus node to generate a list of templates that
reconcile what is "wrong" with the Graph with respect to the GraphDiffs.
Expand All @@ -407,21 +407,16 @@ def diffset_to_templates(diffset: Set[GraphDiff]) -> List["Template"]:
"""
from buildingmotif.dataclasses import Library, Template

related: Dict[URIRef, Set[GraphDiff]] = defaultdict(set)
unfocused: List[Template] = []
lib = Library.create(f"resolve_{token_hex(4)}")
# compute the GROUP BY GraphDiff.focus
for diff in diffset:
if diff.focus is None:
unfocused.extend(diff.resolve(lib))
else:
related[diff.focus].add(diff)

templates = []
# add all templates that don't have a focus node
templates.extend(unfocused)
# now merge all tempaltes together for each focus node
for focus, diffset in related.items():
for focus, diffset in grouped_diffset.items():
if focus is None:
for diff in diffset:
templates.extend(diff.resolve(lib))
continue

templ_lists = (diff.resolve(lib) for diff in diffset)
templs = list(filter(None, chain.from_iterable(templ_lists)))
if len(templs) <= 1:
Expand Down
47 changes: 28 additions & 19 deletions notebooks/Existing-model-validation-example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,23 @@
"output_type": "stream",
"text": [
"Model is valid: False\n",
"Reasons why:\n",
" -http://example.org/building/5-Zone-PVAV-2 needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n",
" -http://example.org/building/5-Zone-PVAV needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n",
" -http://example.org/building/5-Zone-PVAV-1 needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n"
"http://example.org/building/5-Zone-PVAV\n",
" - http://example.org/building/5-Zone-PVAV needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n",
"http://example.org/building/5-Zone-PVAV-1\n",
" - http://example.org/building/5-Zone-PVAV-1 needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n",
"http://example.org/building/5-Zone-PVAV-2\n",
" - http://example.org/building/5-Zone-PVAV-2 needs between 1 and None instances of https://brickschema.org/schema/Brick#Supply_Air_Temperature_Setpoint on path https://brickschema.org/schema/Brick#hasPoint\n"
]
}
],
"source": [
"print(f\"Model is valid: {validation_context.valid}\")\n",
"\n",
"if not validation_context.valid:\n",
" print(\"Reasons why:\")\n",
" for diff in validation_context.diffset:\n",
" print(\" -\" + diff.reason())"
" for focus_node, diffs in validation_context.diffset.items():\n",
" print(focus_node)\n",
" for diff in diffs:\n",
" print(\" - \" + diff.reason())"
]
},
{
Expand Down Expand Up @@ -252,29 +255,32 @@
"@prefix P: <urn:___param___#> .\n",
"@prefix brick: <https://brickschema.org/schema/Brick#> .\n",
"\n",
"<http://example.org/building/5-Zone-PVAV-2> brick:hasPoint P:p1 .\n",
"<http://example.org/building/5-Zone-PVAV> brick:hasPoint P:p1 .\n",
"\n",
"P:p1 a brick:Supply_Air_Temperature_Setpoint .\n",
"\n",
"\n",
"Please enter the value for parameter \"p1\":p1\n",
"--------------------------------------------------------------------------------\n",
"@prefix P: <urn:___param___#> .\n",
"@prefix brick: <https://brickschema.org/schema/Brick#> .\n",
"\n",
"<http://example.org/building/5-Zone-PVAV> brick:hasPoint P:p2 .\n",
"<http://example.org/building/5-Zone-PVAV-1> brick:hasPoint P:p2 .\n",
"\n",
"P:p2 a brick:Supply_Air_Temperature_Setpoint .\n",
"\n",
"\n",
"Please enter the value for parameter \"p2\":p2\n",
"--------------------------------------------------------------------------------\n",
"@prefix P: <urn:___param___#> .\n",
"@prefix brick: <https://brickschema.org/schema/Brick#> .\n",
"\n",
"<http://example.org/building/5-Zone-PVAV-1> brick:hasPoint P:p3 .\n",
"<http://example.org/building/5-Zone-PVAV-2> brick:hasPoint P:p3 .\n",
"\n",
"P:p3 a brick:Supply_Air_Temperature_Setpoint .\n",
"\n",
"\n"
"\n",
"Please enter the value for parameter \"p3\":p3\n"
]
}
],
Expand Down Expand Up @@ -375,10 +381,12 @@
"\t Conformance: True\n",
"Fault Condition: Too many changes in Operating State\n",
"\t Conformance: False\n",
"\t Reasons why:\n",
"\t- http://example.org/building/5-Zone-PVAV-2 needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"\t- http://example.org/building/5-Zone-PVAV-1 needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"\t- http://example.org/building/5-Zone-PVAV needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"http://example.org/building/5-Zone-PVAV-1\n",
" - http://example.org/building/5-Zone-PVAV-1 needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"http://example.org/building/5-Zone-PVAV-2\n",
" - http://example.org/building/5-Zone-PVAV-2 needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"http://example.org/building/5-Zone-PVAV\n",
" - http://example.org/building/5-Zone-PVAV needs between 1 and None instances of https://brickschema.org/schema/Brick#Operating_Mode_Status on path https://brickschema.org/schema/Brick#hasPoint\n",
"Fault Condition: SAT too low; should be higher than MAT\n",
"\t Conformance: True\n",
"Fault Condition: OA fraction is too low or too high; should equal %OA_min\n",
Expand All @@ -396,9 +404,10 @@
" print(f\"Fault Condition: {label}\")\n",
" print(f\"\\t Conformance: {validation_context.valid}\")\n",
" if not validation_context.valid:\n",
" print(\"\\t Reasons why:\")\n",
" for diff in validation_context.diffset:\n",
" print(f\"\\t- {diff.reason()}\")"
" for focus_node, diffs in validation_context.diffset.items():\n",
" print(focus_node)\n",
" for diff in diffs:\n",
" print(\" - \" + diff.reason())"
]
},
{
Expand Down Expand Up @@ -429,7 +438,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
"version": "3.10.0"
}
},
"nbformat": 4,
Expand Down
Loading

0 comments on commit 87b37fb

Please sign in to comment.