Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/gtf-template-inline-fix' into gt…
Browse files Browse the repository at this point in the history
…f-add-new-model-builder
  • Loading branch information
gtfierro committed Dec 6, 2023
2 parents 41c650c + 28a950d commit 33eb00b
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 23 deletions.
53 changes: 35 additions & 18 deletions buildingmotif/dataclasses/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,28 +278,44 @@ def inline_dependencies(self) -> "Template":
replace_nodes(
deptempl.body, {PARAM[k]: PARAM[v] for k, v in rename_params.items()}
)
# rename the optional_args in the dependency template too
deptempl.optional_args = [
rename_params.get(arg, arg) for arg in deptempl.optional_args
]

# at this point, deptempl's parameters are all unique with respect to
# the parent template. They are either renamed explicitly via the dependency's
# args or implicitly via prefixing with the 'name' parameter.

# Next, we need to determine which of deptempl's parameters are optional
# and add these to the parent template's optional_args list.

# get the parent template's optional args
templ_optional_args = set(templ.optional_args)
# figure out which of deptempl's parameters are encoded as 'optional' by the
# parent (depending) template
deptempl_opt_args = deptempl.parameters.intersection(templ.optional_args)
# if the 'name' of the deptempl is optional, then all the arguments inside deptempl
# become optional

# represents the optional parameters of the dependency template
deptempl_opt_args: Set[str] = set()

# these optional parameters come from two places.
# 1. the dependency template itself (its optional_args)
deptempl_opt_args.update(deptempl.optional_args)
# 1a. remove any parameters that have the same name as a parameter in the
# parent but are not optional in the parent
deptempl_opt_args.difference_update(templ.parameters)
# 2. having the same name as an optional parameter in the parent template
# (templ_optional_args)
deptempl_opt_args.update(
templ_optional_args.intersection(deptempl.parameters)
)
# 2a. if the 'name' of the deptempl is optional (given by the parent template),
# then all the arguments inside deptempl become optional
# (deptempl.parameters)
if rename_params["name"] in deptempl_opt_args:
# mark all of deptempl's parameters as optional
templ_optional_args.update(deptempl.parameters)
else:
# otherwise, only add the parameters that are explicitly
# marked as optional *and* appear in this dependency
templ_optional_args.update(deptempl_opt_args)
# ensure that the optional_args includes all params marked as
# optional by the dependency
templ_optional_args.update(
[rename_params[n] for n in deptempl.optional_args]
)
deptempl_opt_args.update(deptempl.parameters)

# convert our set of optional params to a list and assign to the parent template
templ.optional_args = list(templ_optional_args)
templ.optional_args = list(templ_optional_args.union(deptempl_opt_args))

# append the inlined template into the parent's body
templ.body += deptempl.body
Expand Down Expand Up @@ -356,7 +372,8 @@ def evaluate(
)
# true if all parameters are now bound or only optional args are unbound
if len(templ.parameters) == 0 or (
not require_optional_args and templ.parameters == set(self.optional_args)
not require_optional_args
and templ.parameters.issubset(set(self.optional_args))
):
bind_prefixes(templ.body)
if namespaces:
Expand Down Expand Up @@ -392,7 +409,7 @@ def fill(
for param in self.parameters
if include_optional or param not in self.optional_args
}
res = self.evaluate(bindings)
res = self.evaluate(bindings, require_optional_args=include_optional)
assert isinstance(res, rdflib.Graph)
return bindings, res

Expand Down
8 changes: 5 additions & 3 deletions buildingmotif/ingresses/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ def graph(self, ns: Namespace) -> Graph:
assert records is not None
for rec in records:
bindings = {self.mapper(k): _get_term(v, ns) for k, v in rec.fields.items()}
graph = self.template.evaluate(bindings)
assert isinstance(graph, Graph)
graph = self.template.evaluate(bindings, require_optional_args=True)
if not isinstance(graph, Graph):
bindings, graph = graph.fill(ns, include_optional=True)
g += graph
return g

Expand Down Expand Up @@ -121,7 +122,8 @@ def graph(self, ns: Namespace) -> Graph:
template = template.inline_dependencies()
bindings = {self.mapper(k): _get_term(v, ns) for k, v in rec.fields.items()}
graph = template.evaluate(bindings)
assert isinstance(graph, Graph)
if not isinstance(graph, Graph):
_, graph = graph.fill(ns)
g += graph
return g

Expand Down
1 change: 1 addition & 0 deletions tests/unit/fixtures/templates/smalloffice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ opt-vav:
brick:isPointOf P:zone .
optional:
- occ
- zone
16 changes: 14 additions & 2 deletions tests/unit/test_template_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,12 @@ def test_template_evaluate_with_optional(bm: BuildingMOTIF):
lib = Library.load(directory="tests/unit/fixtures/templates")
templ = lib.get_template_by_name("opt-vav")
assert templ.parameters == {"name", "occ", "zone"}
assert templ.optional_args == ["occ"]
assert templ.optional_args == ["occ", "zone"]

g = templ.evaluate({"name": BLDG["vav"]})
assert isinstance(g, Graph)
assert graph_size(g) == 1

g = templ.evaluate({"name": BLDG["vav"], "zone": BLDG["zone1"]})
assert isinstance(g, Graph)
assert graph_size(g) == 1
Expand All @@ -192,8 +197,15 @@ def test_template_evaluate_with_optional(bm: BuildingMOTIF):
assert isinstance(t, Template)
assert t.parameters == {"occ"}

# assert no warning is raised when optional args are not required
with pytest.warns(None) as record:
t = templ.evaluate({"name": BLDG["vav"]})
assert len(record) == 0

with pytest.warns():
partial_templ = templ.evaluate({"name": BLDG["vav"]})
partial_templ = templ.evaluate(
{"name": BLDG["vav"]}, require_optional_args=True
)
assert isinstance(partial_templ, Template)
g = partial_templ.evaluate({"zone": BLDG["zone1"]})
assert isinstance(g, Graph)
Expand Down

0 comments on commit 33eb00b

Please sign in to comment.