From 40254811bd94236df8c875bc40b51c0bcdb5187c Mon Sep 17 00:00:00 2001 From: beam Date: Sat, 24 Oct 2026 15:39:31 +0000 Subject: [PATCH] beam: topo_sort module + tests --- wizard-work/beam/test_toposort.py | 46 +++++++++++++++++++++++++++++++ wizard-work/beam/toposort.py | 4 +++ 2 files changed, 50 insertions(+) create mode 100644 wizard-work/beam/test_toposort.py diff --git a/wizard-work/beam/test_toposort.py b/wizard-work/beam/test_toposort.py new file mode 100644 index 0000000..2509ed4 --- /dev/null +++ b/wizard-work/beam/test_toposort.py @@ -0,0 +1,46 @@ +import sys +sys.path.insert(0, '/root/.hermes/wizard-outputs/beam') +from toposort import topo_sort + +def check(graph, expected_order=None): + r = topo_sort(graph) + if expected_order is not None: + assert r.cycle is None, (graph, r) + assert r.sorted == expected_order, (graph, r.sorted, expected_order) + return r + +# DAG simple +check({'a': ['b'], 'b': []}, ['b','a']) +# disconnected +check({'a': [], 'b': []}, ['a','b']) +# empty +check({}, []) +# diamond +check({'a':['b','c'],'b':['d'],'c':['d'],'d':[]}, ['d','b','c','a'] if False else None) or True +r = topo_sort({'a':['b','c'],'b':['d'],'c':['d'],'d':[]}) +assert r.cycle is None +# verify validity +pos = {n:i for i,n in enumerate(r.sorted)} +for n,deps in {'a':['b','c'],'b':['d'],'c':['d'],'d':[]}.items(): + for d in deps: + assert pos[d] < pos[n] +# cycle +r = topo_sort({'a':['b'],'b':['a']}) +assert r.sorted is None +assert r.cycle is not None +# cycle is valid: each consecutive edge exists and last->first +g={'a':['b'],'b':['a']} +cyc=r.cycle +for i in range(len(cyc)): + u=cyc[i]; v=cyc[(i+1)%len(cyc)] + assert v in g.get(u,[]), (u,v,cyc) +# self loop +r = topo_sort({'a':['a']}) +assert r.sorted is None and r.cycle == ['a'] +# node only as successor +r = topo_sort({'a':['b']}) +assert r.sorted == ['b','a'] +def test_all(): + pass + +print("ALL PASS") diff --git a/wizard-work/beam/toposort.py b/wizard-work/beam/toposort.py index de77f22..4da0d8e 100644 --- a/wizard-work/beam/toposort.py +++ b/wizard-work/beam/toposort.py @@ -26,12 +26,16 @@ def topo_sort(graph: Mapping[Any, Iterable[Any]]) -> TopoResult: # Normalize: ensure every referenced node exists as a key. nodes: set[Any] = set(graph.keys()) for deps in graph.values(): + if deps is None: + continue for d in deps: nodes.add(d) # adjacency: node -> set of dependencies deps_of: dict[Any, set[Any]] = {n: set() for n in nodes} for n, ds in graph.items(): + if ds is None: + ds = [] for d in ds: if d == n: # self-loop is a cycle of length 1