From b64e61d2de65026c46f70cb91e3cb2213c336be0 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 11 Jun 2025 17:30:56 -0400 Subject: [PATCH] clusterlin: abstract try-permutations into ExhaustiveLinearize function Rather than this exhaustive linearization check happening inline inside clusterlin_simple_linearize, abstract it out into a Linearize()-like function for clarity. Note that this isn't exactly a refactor, because the old code would compare the found linearization against all (valid) permutations, while the new code instead first computes the best linearization from all valid permutations, and then compares it with the found one. --- src/test/fuzz/cluster_linearize.cpp | 89 ++++++++++++++++++----------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index b0d3a2ce8f9..579fe955f43 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -168,6 +168,58 @@ std::pair, bool> SimpleLinearize(const DepGraph +std::vector ExhaustiveLinearize(const DepGraph& depgraph) +{ + // The best linearization so far, and its chunking. + std::vector linearization; + std::vector chunking; + + std::vector perm_linearization; + // Initialize with the lexicographically-first linearization. + for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i); + // Iterate over all valid permutations. + do { + /** What prefix of perm_linearization is topological. */ + DepGraphIndex topo_length{0}; + TestBitSet perm_done; + while (topo_length < perm_linearization.size()) { + auto i = perm_linearization[topo_length]; + perm_done.Set(i); + if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break; + ++topo_length; + } + if (topo_length == perm_linearization.size()) { + // If all of perm_linearization is topological, check if it is perhaps our best + // linearization so far. + auto perm_chunking = ChunkLinearization(depgraph, perm_linearization); + auto cmp = CompareChunks(perm_chunking, chunking); + // If the diagram is better, or if it is equal but with more chunks (because we + // prefer minimal chunks), consider this better. + if (linearization.empty() || cmp > 0 || (cmp == 0 && perm_chunking.size() > chunking.size())) { + linearization = perm_linearization; + chunking = perm_chunking; + } + } else { + // Otherwise, fast forward to the last permutation with the same non-topological + // prefix. + auto first_non_topo = perm_linearization.begin() + topo_length; + assert(std::is_sorted(first_non_topo + 1, perm_linearization.end())); + std::reverse(first_non_topo + 1, perm_linearization.end()); + } + } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end())); + + return linearization; +} + + /** Stitch connected components together in a DepGraph, guaranteeing its corresponding cluster is connected. */ template void MakeConnected(DepGraph& depgraph) @@ -996,38 +1048,11 @@ FUZZ_TARGET(clusterlin_simple_linearize) // If SimpleLinearize claims optimal result, and the cluster is sufficiently small (there are // n! linearizations), test that the result is as good as every valid linearization. if (optimal && depgraph.TxCount() <= 8) { - std::vector perm_linearization; - // Initialize with the lexicographically-first linearization. - for (DepGraphIndex i : depgraph.Positions()) perm_linearization.push_back(i); - // Iterate over all valid permutations. - do { - /** What prefix of perm_linearization is topological. */ - DepGraphIndex topo_length{0}; - TestBitSet perm_done; - while (topo_length < perm_linearization.size()) { - auto i = perm_linearization[topo_length]; - perm_done.Set(i); - if (!depgraph.Ancestors(i).IsSubsetOf(perm_done)) break; - ++topo_length; - } - if (topo_length == perm_linearization.size()) { - // If all of perm_linearization is topological, verify that the obtained - // linearization is no worse than it. - auto perm_chunking = ChunkLinearization(depgraph, perm_linearization); - auto cmp = CompareChunks(simple_chunking, perm_chunking); - assert(cmp >= 0); - // If perm_chunking is diagram-optimal, it cannot have more chunks than - // simple_chunking (as simple_chunking claims to be optimal, which implies minimal - // chunks. - if (cmp == 0) assert(simple_chunking.size() >= perm_chunking.size()); - } else { - // Otherwise, fast forward to the last permutation with the same non-topological - // prefix. - auto first_non_topo = perm_linearization.begin() + topo_length; - assert(std::is_sorted(first_non_topo + 1, perm_linearization.end())); - std::reverse(first_non_topo + 1, perm_linearization.end()); - } - } while(std::next_permutation(perm_linearization.begin(), perm_linearization.end())); + auto exh_linearization = ExhaustiveLinearize(depgraph); + auto exh_chunking = ChunkLinearization(depgraph, exh_linearization); + auto cmp = CompareChunks(simple_chunking, exh_chunking); + assert(cmp == 0); + assert(simple_chunking.size() == exh_chunking.size()); } if (optimal) {