fix(inspect): choose selectors that share one condition together

This commit is contained in:
Vito Sansevero
2026-10-19 15:00:33 -07:00
parent 7fb8f973b4
commit a05109b86e
2 changed files with 50 additions and 9 deletions
+41 -2
View File
@@ -1481,13 +1481,48 @@ class TestConditioningSemantics(unittest.TestCase):
[(parity.POS, parity.POS), (WOLF, WOLF)],
)
# Independent selectors on each role multiply.
source["7"] = _if_else(["12", 1], ["10", 0], ["14", 0])
source["7"] = _if_else(["16", 1], ["10", 0], ["14", 0])
source["14"] = _encoded(parity.NEG)
source["16"] = _image_exists()
source["3"]["inputs"]["negative"] = ["7", 0]
candidates = _inspect(source)["candidates"]
self.assertEqual(len(candidates), 4)
self.assertEqual(len({_sides(item) for item in candidates}), 4)
def test_selectors_sharing_one_condition_choose_together(self):
extra = {
"10": _encoded("pos A"),
"11": _encoded("pos B"),
"14": _encoded("neg A"),
"15": _encoded("neg B"),
"12": _image_exists(),
"16": _image_exists(),
}
source = _with_positive(_if_else(["12", 1], ["10", 0], ["11", 0]), extra)
source["7"] = _if_else(["12", 1], ["14", 0], ["15", 0])
result = _inspect(source)
self.assertEqual(result["state"], "ambiguous")
# One runtime value drives both selectors: never "pos A" with "neg B".
self.assertEqual(
[_sides(item) for item in result["candidates"]],
[("pos A", "neg A"), ("pos B", "neg B")],
)
# Another output slot of the same node is another value.
source["7"]["inputs"]["boolean"] = ["12", 0]
self.assertEqual(len(_inspect(source)["candidates"]), 4)
# Two independent conditions give the full cross product.
source["7"]["inputs"]["boolean"] = ["16", 1]
candidates = _inspect(source)["candidates"]
self.assertEqual(
sorted(_sides(item) for item in candidates),
[
("pos A", "neg A"),
("pos A", "neg B"),
("pos B", "neg A"),
("pos B", "neg B"),
],
)
def test_diverging_branch_that_cannot_be_traced_is_never_guessed(self):
extra = {
"10": _encoded(parity.POS),
@@ -1505,7 +1540,11 @@ class TestConditioningSemantics(unittest.TestCase):
for index in range(6):
extra[f"a{index}"] = _encoded(f"left {index}")
extra[f"b{index}"] = _encoded(f"right {index}")
extra[f"s{index}"] = _if_else(["12", 1], [f"a{index}", 0], [f"b{index}", 0])
# Each selector reads its own condition: independent choices.
extra[f"e{index}"] = _image_exists()
extra[f"s{index}"] = _if_else(
[f"e{index}", 1], [f"a{index}", 0], [f"b{index}", 0]
)
extra[f"c{index}"] = {
"class_type": "ConditioningCombine",
"inputs": {
+9 -7
View File
@@ -171,9 +171,11 @@ class _Diverging(Exception):
again once per choice, so each candidate is internally consistent.
"""
def __init__(self, node_id: str) -> None:
super().__init__(node_id)
self.node_id = node_id
def __init__(self, key: Tuple[str, int]) -> None:
super().__init__(key)
# The condition output (node ID, slot): one runtime value, so every
# selector it drives is forced together.
self.key = key
def _node_ref(node_id: str, class_type: Any) -> dict:
@@ -599,8 +601,8 @@ class _GraphResolver:
choice: Optional[bool] = condition
elif not _link(condition):
raise _Unresolved("Malformed selector condition", "malformed")
elif node_id in self.choices:
choice = self.choices[node_id]
elif (str(condition[0]), condition[1]) in self.choices:
choice = self.choices[(str(condition[0]), condition[1])]
self.tainted = True
else:
choice = None
@@ -614,7 +616,7 @@ class _GraphResolver:
for label in ("true", "false")
]
if parts[0][0] != parts[1][0]:
raise _Diverging(node_id)
raise _Diverging((str(condition[0]), condition[1]))
return self.merged(node_id, 0, parts)
def merged(self, node_id: str, slot: int, parts: list) -> Tuple[str, list, str]:
@@ -939,7 +941,7 @@ class _GraphResolver:
if len(results) + len(pending) + 2 > _MAX_BRANCH_CANDIDATES:
raise _Unresolved("Too many selector branches", "work-limit")
pending.extend(
{**self.choices, diverging.node_id: choice}
{**self.choices, diverging.key: choice}
for choice in (True, False)
)
except _Unresolved as error: