Optimise IfcPatch recipe: make toposort backend configurable

aothms asked for the toposort dependency ordering used by the dedup
walk to try igraph's C-backed topological_sorting() first, since it
should shave off additional time on top of the non-recursive
get_info fix. Falls back to the pure python toposort package with a
warning if igraph is not installed.

Generated with the assistance of an AI coding tool.

(cherry picked from commit 7ebdd046b6)
This commit is contained in:
Petru Conduraru
2026-07-18 23:00:58 +03:00
committed by Dion Moult
parent 4299968b6e
commit 6dba261e1b
2 changed files with 78 additions and 3 deletions
+46
View File
@@ -18,11 +18,18 @@
# This file was generated with the assistance of an AI coding tool.
import logging
import sys
from unittest import mock
import pytest
import ifcopenshell
import ifcopenshell.guid
import ifcpatch
import test.bootstrap
from ifcpatch.recipes.Optimise import _toposort
def add_context(f: ifcopenshell.file) -> ifcopenshell.entity_instance:
@@ -71,3 +78,42 @@ class TestOptimise(test.bootstrap.IFC4):
assert len(output.by_type("IfcPolyline")) == 2
walls_after = output.by_type("IfcWall")
assert curve_of(walls_after[0]) != curve_of(walls_after[1])
def test_folding_still_works_on_the_pure_python_fallback(self):
context = add_context(self.file)
coords = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]
add_wall_with_curve(self.file, context, coords)
add_wall_with_curve(self.file, context, coords)
with mock.patch.dict(sys.modules, {"igraph": None}):
output = ifcpatch.execute({"file": self.file, "recipe": "Optimise", "arguments": []})
assert len(output.by_type("IfcPolyline")) == 1
GRAPH = {4: {2, 3}, 2: {1}, 3: {1}, 1: set()}
def assert_dependency_order(order: list[int], graph: dict[int, set[int]]) -> None:
position = {id_: i for i, id_ in enumerate(order)}
assert sorted(order) == sorted(graph)
for id_, references in graph.items():
for reference in references:
assert position[reference] < position[id_]
class TestToposortBackends:
def test_igraph_backend_orders_dependencies_first(self):
pytest.importorskip("igraph")
assert_dependency_order(_toposort(GRAPH, logging.getLogger(__name__)), GRAPH)
def test_igraph_backend_includes_references_missing_from_the_keys(self):
pytest.importorskip("igraph")
assert _toposort({2: {1}}, logging.getLogger(__name__)) == [1, 2]
def test_pure_python_fallback_warns_when_igraph_is_unavailable(self, caplog):
with mock.patch.dict(sys.modules, {"igraph": None}):
with caplog.at_level(logging.WARNING):
order = _toposort(GRAPH, logging.getLogger(__name__))
assert_dependency_order(order, GRAPH)
assert "python-igraph" in caplog.text