Skip to content

Commit 1b5e0f6

Browse files
committed
perf: hoist loop-invariant graph conversions out of the monomorphism search
generate_all_subgraphs rebuilt the template's nx.DiGraph, and re-read T.all_nodes(), inside the inner loop -- once for each of the 2^|nodes| node subsets, though both are the same every time. `.subgraph()` returns a read-only view, so one conversion serves them all. On an 11-node template that is 2036 conversions instead of 1. TemplateMatcher._generate_mappings had the same shape and the more expensive version of it: every candidate subgraph built a _VF2SemanticMatcher, and each of those converted the whole *building* graph again. It now converts once and passes the result down through a new optional T_digraph argument; the matcher only reads the graph, so sharing one instance is safe. Output is unchanged -- with a fixed PYTHONHASHSEED, generate_all_subgraphs yields a byte-identical sequence before and after. Matching a 4-node guideline36 template against a 6k-triple model goes from 7.66s to 6.36s; the remainder is the VF2 search itself, which this does not touch.
1 parent 64ae7a0 commit 1b5e0f6

1 file changed

Lines changed: 32 additions & 7 deletions

File tree

buildingmotif/template_matcher.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,24 @@ class _VF2SemanticMatcher(DiGraphMatcher):
193193
:param T: template graph
194194
:param G: building graph
195195
:param ontology: ontology that contains the information about node semantics
196+
:param T_digraph: ``T`` already converted to a ``nx.DiGraph``. Callers that
197+
build many matchers against the *same* ``T`` should convert once and
198+
pass it here; the conversion is O(len(T)) and is otherwise repeated per
199+
matcher. The matcher only reads the graph, so one instance is safely
200+
shared.
196201
"""
197202

198-
def __init__(self, T: Graph, G: Graph, ontology: Graph):
199-
super().__init__(rdflib_to_networkx_digraph(T), rdflib_to_networkx_digraph(G))
203+
def __init__(
204+
self,
205+
T: Graph,
206+
G: Graph,
207+
ontology: Graph,
208+
T_digraph: Optional[nx.DiGraph] = None,
209+
):
210+
super().__init__(
211+
rdflib_to_networkx_digraph(T) if T_digraph is None else T_digraph,
212+
rdflib_to_networkx_digraph(G),
213+
)
200214
self.ontology = ontology
201215
self._cache = _ontology_lookup_cache()
202216
self._semantic_feasibility = get_semantic_feasibility(
@@ -222,11 +236,15 @@ def generate_all_subgraphs(T: Graph) -> Generator[Graph, None, None]:
222236
:yield: subgraphs
223237
:rtype: Generator[Graph, None, None]
224238
"""
239+
# Both the node set and the DiGraph conversion are loop-invariant, but were
240+
# recomputed for every one of the 2^|nodes| subsets -- and `.subgraph()`
241+
# returns a read-only view, so a single conversion serves them all.
242+
nodes = list(T.all_nodes())
243+
digraph = rdflib_to_networkx_digraph(T)
225244
# no monomorphism will be larger than the number of distinct nodes in the graph
226-
largest_sg_size = len(T.all_nodes())
227-
for nodecount in range(largest_sg_size, 1, -1):
228-
for nodelist in combinations(T.all_nodes(), nodecount):
229-
yield digraph_to_rdflib(rdflib_to_networkx_digraph(T).subgraph(nodelist))
245+
for nodecount in range(len(nodes), 1, -1):
246+
for nodelist in combinations(nodes, nodecount):
247+
yield digraph_to_rdflib(digraph.subgraph(nodelist))
230248

231249

232250
def digraph_to_rdflib(digraph: nx.DiGraph) -> Graph:
@@ -279,9 +297,16 @@ def __init__(
279297
self._generate_mappings()
280298

281299
def _generate_mappings(self):
300+
# `self.building` is fixed across every candidate subgraph, so convert it
301+
# once rather than once per subgraph: the loop runs 2^|template nodes|
302+
# times and the building graph is the large one of the pair.
303+
building_digraph = rdflib_to_networkx_digraph(self.building)
282304
for template_subgraph in generate_all_subgraphs(self.template_graph):
283305
matching = _VF2SemanticMatcher(
284-
self.building, template_subgraph, self.ontology
306+
self.building,
307+
template_subgraph,
308+
self.ontology,
309+
T_digraph=building_digraph,
285310
)
286311
if matching.subgraph_is_monomorphic():
287312
for sg in matching.subgraph_monomorphisms_iter():

0 commit comments

Comments
 (0)