diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 35922ed4e..1c0e5f785 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -93,6 +93,7 @@ "BMF": [Boolean Matrix Factorization], "PaintShop": [Paint Shop], "BicliqueCover": [Biclique Cover], + "BalancedCompleteBipartiteSubgraph": [Balanced Complete Bipartite Subgraph], "BoundedComponentSpanningForest": [Bounded Component Spanning Forest], "BinPacking": [Bin Packing], "ClosestVectorProblem": [Closest Vector Problem], @@ -1926,6 +1927,72 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS ] } +#{ + let x = load-model-example("BalancedCompleteBipartiteSubgraph") + let left-size = x.instance.graph.left_size + let right-size = x.instance.graph.right_size + let k = x.instance.k + let bip-edges = x.instance.graph.edges + let sol = x.optimal.at(0) + let left-selected = range(left-size).filter(i => sol.config.at(i) == 1) + let right-selected = range(right-size).filter(i => sol.config.at(left-size + i) == 1) + let selected-edges = bip-edges.filter(e => + left-selected.contains(e.at(0)) and right-selected.contains(e.at(1)) + ) + [ + #problem-def("BalancedCompleteBipartiteSubgraph")[ + Given a bipartite graph $G = (A, B, E)$ and an integer $k$, determine whether there exist subsets $A' subset.eq A$ and $B' subset.eq B$ such that $|A'| = |B'| = k$ and every cross pair is present: + $A' times B' subset.eq E.$ + ][ + Balanced Complete Bipartite Subgraph is a classical NP-complete bipartite containment problem from Garey and Johnson @garey1979. Unlike Biclique Cover, which asks for a collection of bicliques covering all edges, this problem asks for a _single_ balanced biclique of prescribed size. It arises naturally in biclustering, dense submatrix discovery, and pattern mining on bipartite data. Chen et al. give an exact $O^*(1.3803^n)$ algorithm for dense bipartite graphs, and the registry records that best-known bound in the catalog metadata. A straightforward baseline still enumerates all $k$-subsets of $A$ and $B$ and checks whether they induce a complete bipartite graph, taking $O(binom(|A|, k) dot binom(|B|, k) dot k^2) = O^*(2^(|A| + |B|))$ time. + + *Example.* Consider the bipartite graph with $A = {ell_1, ell_2, ell_3, ell_4}$, $B = {r_1, r_2, r_3, r_4}$, and edges $E = {#bip-edges.map(e => $(ell_#(e.at(0) + 1), r_#(e.at(1) + 1))$).join(", ")}$. For $k = #k$, the selected sets $A' = {#left-selected.map(i => $ell_#(i + 1)$).join(", ")}$ and $B' = {#right-selected.map(i => $r_#(i + 1)$).join(", ")}$ form a balanced complete bipartite subgraph: all #selected-edges.len() required cross edges are present. Vertex $ell_4$ is excluded because $(ell_4, r_3) in.not E$, so any witness using $ell_4$ cannot realize $K_(#k,#k)$. + + #figure( + canvas(length: 1cm, { + let lpos = range(left-size).map(i => (0, left-size - 1 - i)) + let rpos = range(right-size).map(i => (2.6, right-size - 1 - i)) + for (li, rj) in bip-edges { + let selected = selected-edges.any(e => e.at(0) == li and e.at(1) == rj) + g-edge( + lpos.at(li), + rpos.at(rj), + stroke: if selected { 2pt + graph-colors.at(0) } else { 1pt + luma(180) }, + ) + } + for (idx, pos) in lpos.enumerate() { + let selected = left-selected.contains(idx) + g-node( + pos, + name: "bcbs-l" + str(idx), + fill: if selected { graph-colors.at(0) } else { luma(240) }, + label: if selected { + text(fill: white)[$ell_#(idx + 1)$] + } else { + [$ell_#(idx + 1)$] + }, + ) + } + for (idx, pos) in rpos.enumerate() { + let selected = right-selected.contains(idx) + g-node( + pos, + name: "bcbs-r" + str(idx), + fill: if selected { graph-colors.at(0) } else { luma(240) }, + label: if selected { + text(fill: white)[$r_#(idx + 1)$] + } else { + [$r_#(idx + 1)$] + }, + ) + } + }), + caption: [Balanced complete bipartite subgraph with $k = #k$: the selected vertices $A' = {#left-selected.map(i => $ell_#(i + 1)$).join(", ")}$ and $B' = {#right-selected.map(i => $r_#(i + 1)$).join(", ")}$ are blue, and the 9 edges of the induced $K_(#k,#k)$ are highlighted. The missing edge $(ell_4, r_3)$ prevents including $ell_4$.], + ) + ] + ] +} + #{ let x = load-model-example("PartitionIntoTriangles") let nv = graph-num-vertices(x.instance) diff --git a/docs/src/reductions/problem_schemas.json b/docs/src/reductions/problem_schemas.json new file mode 100644 index 000000000..7cd236022 --- /dev/null +++ b/docs/src/reductions/problem_schemas.json @@ -0,0 +1,1004 @@ +[ + { + "name": "BMF", + "description": "Boolean matrix factorization", + "fields": [ + { + "name": "matrix", + "type_name": "Vec>", + "description": "Target boolean matrix A" + }, + { + "name": "k", + "type_name": "usize", + "description": "Factorization rank" + } + ] + }, + { + "name": "BalancedCompleteBipartiteSubgraph", + "description": "Decide whether a bipartite graph contains a K_{k,k} subgraph", + "fields": [ + { + "name": "graph", + "type_name": "BipartiteGraph", + "description": "The bipartite graph G = (A, B, E)" + }, + { + "name": "k", + "type_name": "usize", + "description": "Balanced biclique size" + } + ] + }, + { + "name": "BicliqueCover", + "description": "Cover bipartite edges with k bicliques", + "fields": [ + { + "name": "left_size", + "type_name": "usize", + "description": "Vertices in left partition" + }, + { + "name": "right_size", + "type_name": "usize", + "description": "Vertices in right partition" + }, + { + "name": "edges", + "type_name": "Vec<(usize, usize)>", + "description": "Bipartite edges" + }, + { + "name": "k", + "type_name": "usize", + "description": "Number of bicliques" + } + ] + }, + { + "name": "BiconnectivityAugmentation", + "description": "Add weighted potential edges to make a graph biconnected within budget", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "potential_weights", + "type_name": "Vec<(usize, usize, W)>", + "description": "Potential edges with augmentation weights" + }, + { + "name": "budget", + "type_name": "W::Sum", + "description": "Maximum total augmentation weight B" + } + ] + }, + { + "name": "BinPacking", + "description": "Assign items to bins minimizing number of bins used, subject to capacity", + "fields": [ + { + "name": "sizes", + "type_name": "Vec", + "description": "Item sizes s_i for each item" + }, + { + "name": "capacity", + "type_name": "W", + "description": "Bin capacity C" + } + ] + }, + { + "name": "BoundedComponentSpanningForest", + "description": "Partition vertices into at most K connected components, each of total weight at most B", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w(v) for each vertex v in V" + }, + { + "name": "max_components", + "type_name": "usize", + "description": "Upper bound K on the number of connected components" + }, + { + "name": "max_weight", + "type_name": "W::Sum", + "description": "Upper bound B on the total weight of each component" + } + ] + }, + { + "name": "CircuitSAT", + "description": "Find satisfying input to a boolean circuit", + "fields": [ + { + "name": "circuit", + "type_name": "Circuit", + "description": "The boolean circuit" + } + ] + }, + { + "name": "ClosestVectorProblem", + "description": "Find the closest lattice point to a target vector", + "fields": [ + { + "name": "basis", + "type_name": "Vec>", + "description": "Basis matrix B as column vectors" + }, + { + "name": "target", + "type_name": "Vec", + "description": "Target vector t" + }, + { + "name": "bounds", + "type_name": "Vec", + "description": "Integer bounds per variable" + } + ] + }, + { + "name": "DirectedTwoCommodityIntegralFlow", + "description": "Two-commodity integral flow feasibility on a directed graph", + "fields": [ + { + "name": "graph", + "type_name": "DirectedGraph", + "description": "Directed graph G = (V, A)" + }, + { + "name": "capacities", + "type_name": "Vec", + "description": "Capacity c(a) for each arc" + }, + { + "name": "source_1", + "type_name": "usize", + "description": "Source vertex s_1 for commodity 1" + }, + { + "name": "sink_1", + "type_name": "usize", + "description": "Sink vertex t_1 for commodity 1" + }, + { + "name": "source_2", + "type_name": "usize", + "description": "Source vertex s_2 for commodity 2" + }, + { + "name": "sink_2", + "type_name": "usize", + "description": "Sink vertex t_2 for commodity 2" + }, + { + "name": "requirement_1", + "type_name": "u64", + "description": "Flow requirement R_1 for commodity 1" + }, + { + "name": "requirement_2", + "type_name": "u64", + "description": "Flow requirement R_2 for commodity 2" + } + ] + }, + { + "name": "ExactCoverBy3Sets", + "description": "Determine if a collection of 3-element subsets contains an exact cover", + "fields": [ + { + "name": "universe_size", + "type_name": "usize", + "description": "Size of universe X (must be divisible by 3)" + }, + { + "name": "subsets", + "type_name": "Vec<[usize; 3]>", + "description": "Collection C of 3-element subsets of X" + } + ] + }, + { + "name": "Factoring", + "description": "Factor a composite integer into two factors", + "fields": [ + { + "name": "m", + "type_name": "usize", + "description": "Bits for first factor" + }, + { + "name": "n", + "type_name": "usize", + "description": "Bits for second factor" + }, + { + "name": "target", + "type_name": "u64", + "description": "Number to factor" + } + ] + }, + { + "name": "FlowShopScheduling", + "description": "Determine if a flow-shop schedule for jobs on m processors meets a deadline", + "fields": [ + { + "name": "num_processors", + "type_name": "usize", + "description": "Number of machines m" + }, + { + "name": "task_lengths", + "type_name": "Vec>", + "description": "task_lengths[j][i] = length of job j's task on machine i" + }, + { + "name": "deadline", + "type_name": "u64", + "description": "Global deadline D" + } + ] + }, + { + "name": "GraphPartitioning", + "description": "Find minimum cut balanced bisection of a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The undirected graph G=(V,E)" + } + ] + }, + { + "name": "HamiltonianPath", + "description": "Find a Hamiltonian path in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + } + ] + }, + { + "name": "ILP", + "description": "Optimize linear objective subject to linear constraints", + "fields": [ + { + "name": "num_vars", + "type_name": "usize", + "description": "Number of integer variables" + }, + { + "name": "constraints", + "type_name": "Vec", + "description": "Linear constraints" + }, + { + "name": "objective", + "type_name": "Vec<(usize, f64)>", + "description": "Sparse objective coefficients" + }, + { + "name": "sense", + "type_name": "ObjectiveSense", + "description": "Optimization direction" + } + ] + }, + { + "name": "IsomorphicSpanningTree", + "description": "Does graph G contain a spanning tree isomorphic to tree T?", + "fields": [ + { + "name": "graph", + "type_name": "SimpleGraph", + "description": "The host graph G" + }, + { + "name": "tree", + "type_name": "SimpleGraph", + "description": "The target tree T (must be a tree with |V(T)| = |V(G)|)" + } + ] + }, + { + "name": "KColoring", + "description": "Find valid k-coloring of a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + } + ] + }, + { + "name": "KSatisfiability", + "description": "SAT with exactly k literals per clause", + "fields": [ + { + "name": "num_vars", + "type_name": "usize", + "description": "Number of Boolean variables" + }, + { + "name": "clauses", + "type_name": "Vec", + "description": "Clauses each with exactly K literals" + } + ] + }, + { + "name": "Knapsack", + "description": "Select items to maximize total value subject to weight capacity constraint", + "fields": [ + { + "name": "weights", + "type_name": "Vec", + "description": "Nonnegative item weights w_i" + }, + { + "name": "values", + "type_name": "Vec", + "description": "Nonnegative item values v_i" + }, + { + "name": "capacity", + "type_name": "i64", + "description": "Nonnegative knapsack capacity C" + } + ] + }, + { + "name": "LengthBoundedDisjointPaths", + "description": "Find J internally vertex-disjoint s-t paths of length at most K", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "source", + "type_name": "usize", + "description": "The shared source vertex s" + }, + { + "name": "sink", + "type_name": "usize", + "description": "The shared sink vertex t" + }, + { + "name": "num_paths_required", + "type_name": "usize", + "description": "Required number J of disjoint s-t paths" + }, + { + "name": "max_length", + "type_name": "usize", + "description": "Maximum path length K in edges" + } + ] + }, + { + "name": "LongestCommonSubsequence", + "description": "Find the longest string that is a subsequence of every input string", + "fields": [ + { + "name": "strings", + "type_name": "Vec>", + "description": "The input strings" + } + ] + }, + { + "name": "MaxCut", + "description": "Find maximum weight cut in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The graph with edge weights" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge weights w: E -> R" + } + ] + }, + { + "name": "MaximalIS", + "description": "Find maximum weight maximal independent set", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MaximumClique", + "description": "Find maximum weight clique in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MaximumIndependentSet", + "description": "Find maximum weight independent set in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MaximumMatching", + "description": "Find maximum weight matching in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge weights w: E -> R" + } + ] + }, + { + "name": "MaximumSetPacking", + "description": "Find maximum weight collection of disjoint sets", + "fields": [ + { + "name": "sets", + "type_name": "Vec>", + "description": "Collection of sets over a universe" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Weight for each set" + } + ] + }, + { + "name": "MinimumDominatingSet", + "description": "Find minimum weight dominating set in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MinimumFeedbackArcSet", + "description": "Find minimum weight feedback arc set in a directed graph", + "fields": [ + { + "name": "graph", + "type_name": "DirectedGraph", + "description": "The directed graph G=(V,A)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Arc weights w: A -> R" + } + ] + }, + { + "name": "MinimumFeedbackVertexSet", + "description": "Find minimum weight feedback vertex set in a directed graph", + "fields": [ + { + "name": "graph", + "type_name": "DirectedGraph", + "description": "The directed graph G=(V,A)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MinimumMultiwayCut", + "description": "Find minimum weight set of edges whose removal disconnects all terminal pairs", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The undirected graph G=(V,E)" + }, + { + "name": "terminals", + "type_name": "Vec", + "description": "Terminal vertices that must be separated" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge weights w: E -> R (same order as graph.edges())" + } + ] + }, + { + "name": "MinimumSetCovering", + "description": "Find minimum weight collection covering the universe", + "fields": [ + { + "name": "universe_size", + "type_name": "usize", + "description": "Size of the universe U" + }, + { + "name": "sets", + "type_name": "Vec>", + "description": "Collection of subsets of U" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Weight for each set" + } + ] + }, + { + "name": "MinimumSumMulticenter", + "description": "Find K centers minimizing total weighted distance (p-median problem)", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "vertex_weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + }, + { + "name": "edge_lengths", + "type_name": "Vec", + "description": "Edge lengths l: E -> R" + }, + { + "name": "k", + "type_name": "usize", + "description": "Number of centers to place" + } + ] + }, + { + "name": "MinimumTardinessSequencing", + "description": "Schedule unit-length tasks with precedence constraints and deadlines to minimize the number of tardy tasks", + "fields": [ + { + "name": "num_tasks", + "type_name": "usize", + "description": "Number of tasks |T|" + }, + { + "name": "deadlines", + "type_name": "Vec", + "description": "Deadline d(t) for each task (1-indexed finish time)" + }, + { + "name": "precedences", + "type_name": "Vec<(usize, usize)>", + "description": "Precedence pairs (predecessor, successor)" + } + ] + }, + { + "name": "MinimumVertexCover", + "description": "Find minimum weight vertex cover in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Vertex weights w: V -> R" + } + ] + }, + { + "name": "MultipleChoiceBranching", + "description": "Find a branching with partition constraints and weight at least K", + "fields": [ + { + "name": "graph", + "type_name": "DirectedGraph", + "description": "The directed graph G=(V,A)" + }, + { + "name": "weights", + "type_name": "Vec", + "description": "Arc weights w(a) for each arc a in A" + }, + { + "name": "partition", + "type_name": "Vec>", + "description": "Partition of arc indices; each arc index must appear in exactly one group" + }, + { + "name": "threshold", + "type_name": "W::Sum", + "description": "Weight threshold K" + } + ] + }, + { + "name": "OptimalLinearArrangement", + "description": "Find a vertex ordering on a line with total edge length at most K", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The undirected graph G=(V,E)" + }, + { + "name": "bound", + "type_name": "usize", + "description": "Upper bound K on total edge length" + } + ] + }, + { + "name": "PaintShop", + "description": "Minimize color changes in paint shop sequence", + "fields": [ + { + "name": "sequence", + "type_name": "Vec", + "description": "Car labels (each must appear exactly twice)" + } + ] + }, + { + "name": "PartitionIntoTriangles", + "description": "Partition vertices into triangles (K3 subgraphs)", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E) with |V| divisible by 3" + } + ] + }, + { + "name": "QUBO", + "description": "Minimize quadratic unconstrained binary objective", + "fields": [ + { + "name": "num_vars", + "type_name": "usize", + "description": "Number of binary variables" + }, + { + "name": "matrix", + "type_name": "Vec>", + "description": "Upper-triangular Q matrix" + } + ] + }, + { + "name": "RuralPostman", + "description": "Find a circuit covering required edges with total length at most B (Rural Postman Problem)", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge lengths l(e) for each e in E" + }, + { + "name": "required_edges", + "type_name": "Vec", + "description": "Edge indices of the required subset E' ⊆ E" + }, + { + "name": "bound", + "type_name": "W::Sum", + "description": "Upper bound B on total circuit length" + } + ] + }, + { + "name": "Satisfiability", + "description": "Find satisfying assignment for CNF formula", + "fields": [ + { + "name": "num_vars", + "type_name": "usize", + "description": "Number of Boolean variables" + }, + { + "name": "clauses", + "type_name": "Vec", + "description": "Clauses in conjunctive normal form" + } + ] + }, + { + "name": "SequencingWithinIntervals", + "description": "Schedule tasks non-overlappingly within their time windows", + "fields": [ + { + "name": "release_times", + "type_name": "Vec", + "description": "Release time r(t) for each task" + }, + { + "name": "deadlines", + "type_name": "Vec", + "description": "Deadline d(t) for each task" + }, + { + "name": "lengths", + "type_name": "Vec", + "description": "Processing length l(t) for each task" + } + ] + }, + { + "name": "SetBasis", + "description": "Determine whether a collection of sets admits a basis of size k under union", + "fields": [ + { + "name": "universe_size", + "type_name": "usize", + "description": "Size of the ground set S" + }, + { + "name": "collection", + "type_name": "Vec>", + "description": "Collection C of target subsets of S" + }, + { + "name": "k", + "type_name": "usize", + "description": "Required number of basis sets" + } + ] + }, + { + "name": "ShortestCommonSupersequence", + "description": "Find a common supersequence of bounded length for a set of strings", + "fields": [ + { + "name": "alphabet_size", + "type_name": "usize", + "description": "Size of the alphabet" + }, + { + "name": "strings", + "type_name": "Vec>", + "description": "Input strings over the alphabet {0, ..., alphabet_size-1}" + }, + { + "name": "bound", + "type_name": "usize", + "description": "Bound on supersequence length (configuration has exactly this many symbols)" + } + ] + }, + { + "name": "SpinGlass", + "description": "Minimize Ising Hamiltonian on a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The interaction graph" + }, + { + "name": "couplings", + "type_name": "Vec", + "description": "Pairwise couplings J_ij" + }, + { + "name": "fields", + "type_name": "Vec", + "description": "On-site fields h_i" + } + ] + }, + { + "name": "SteinerTree", + "description": "Find minimum weight tree connecting terminal vertices", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge weights w: E -> R" + }, + { + "name": "terminals", + "type_name": "Vec", + "description": "Terminal vertices T that must be connected" + } + ] + }, + { + "name": "StrongConnectivityAugmentation", + "description": "Add a bounded set of weighted candidate arcs to make a digraph strongly connected", + "fields": [ + { + "name": "graph", + "type_name": "DirectedGraph", + "description": "The initial directed graph G=(V,A)" + }, + { + "name": "candidate_arcs", + "type_name": "Vec<(usize, usize, W)>", + "description": "Candidate augmenting arcs (u, v, w(u,v)) not already present in G" + }, + { + "name": "bound", + "type_name": "W::Sum", + "description": "Upper bound B on the total added weight" + } + ] + }, + { + "name": "SubgraphIsomorphism", + "description": "Determine if host graph G contains a subgraph isomorphic to pattern graph H", + "fields": [ + { + "name": "graph", + "type_name": "SimpleGraph", + "description": "The host graph G = (V_1, E_1) to search in" + }, + { + "name": "pattern", + "type_name": "SimpleGraph", + "description": "The pattern graph H = (V_2, E_2) to find as a subgraph" + } + ] + }, + { + "name": "SubsetSum", + "description": "Find a subset of positive integers that sums to exactly a target value", + "fields": [ + { + "name": "sizes", + "type_name": "Vec", + "description": "Positive integer sizes s(a) for each element" + }, + { + "name": "target", + "type_name": "BigUint", + "description": "Target sum B" + } + ] + }, + { + "name": "TravelingSalesman", + "description": "Find minimum weight Hamiltonian cycle in a graph (Traveling Salesman Problem)", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + }, + { + "name": "edge_weights", + "type_name": "Vec", + "description": "Edge weights w: E -> R" + } + ] + }, + { + "name": "UndirectedTwoCommodityIntegralFlow", + "description": "Determine whether two integral commodities can satisfy sink demands in an undirected capacitated graph", + "fields": [ + { + "name": "graph", + "type_name": "SimpleGraph", + "description": "Undirected graph G=(V,E)" + }, + { + "name": "capacities", + "type_name": "Vec", + "description": "Edge capacities c(e) in graph edge order" + }, + { + "name": "source_1", + "type_name": "usize", + "description": "Source vertex s_1 for commodity 1" + }, + { + "name": "sink_1", + "type_name": "usize", + "description": "Sink vertex t_1 for commodity 1" + }, + { + "name": "source_2", + "type_name": "usize", + "description": "Source vertex s_2 for commodity 2" + }, + { + "name": "sink_2", + "type_name": "usize", + "description": "Sink vertex t_2 for commodity 2" + }, + { + "name": "requirement_1", + "type_name": "u64", + "description": "Required net inflow R_1 at sink t_1" + }, + { + "name": "requirement_2", + "type_name": "u64", + "description": "Required net inflow R_2 at sink t_2" + } + ] + } +] \ No newline at end of file diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json new file mode 100644 index 000000000..756c52b70 --- /dev/null +++ b/docs/src/reductions/reduction_graph.json @@ -0,0 +1,1482 @@ +{ + "nodes": [ + { + "name": "BMF", + "variant": {}, + "category": "algebraic", + "doc_path": "models/algebraic/struct.BMF.html", + "complexity": "2^(rows * rank + rank * cols)" + }, + { + "name": "BalancedCompleteBipartiteSubgraph", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.BalancedCompleteBipartiteSubgraph.html", + "complexity": "1.3803^num_vertices" + }, + { + "name": "BicliqueCover", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.BicliqueCover.html", + "complexity": "2^num_vertices" + }, + { + "name": "BiconnectivityAugmentation", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.BiconnectivityAugmentation.html", + "complexity": "2^num_potential_edges" + }, + { + "name": "BinPacking", + "variant": { + "weight": "f64" + }, + "category": "misc", + "doc_path": "models/misc/struct.BinPacking.html", + "complexity": "2^num_items" + }, + { + "name": "BinPacking", + "variant": { + "weight": "i32" + }, + "category": "misc", + "doc_path": "models/misc/struct.BinPacking.html", + "complexity": "2^num_items" + }, + { + "name": "BoundedComponentSpanningForest", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.BoundedComponentSpanningForest.html", + "complexity": "3^num_vertices" + }, + { + "name": "CircuitSAT", + "variant": {}, + "category": "formula", + "doc_path": "models/formula/struct.CircuitSAT.html", + "complexity": "2^num_variables" + }, + { + "name": "ClosestVectorProblem", + "variant": { + "weight": "f64" + }, + "category": "algebraic", + "doc_path": "models/algebraic/struct.ClosestVectorProblem.html", + "complexity": "2^num_basis_vectors" + }, + { + "name": "ClosestVectorProblem", + "variant": { + "weight": "i32" + }, + "category": "algebraic", + "doc_path": "models/algebraic/struct.ClosestVectorProblem.html", + "complexity": "2^num_basis_vectors" + }, + { + "name": "DirectedTwoCommodityIntegralFlow", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.DirectedTwoCommodityIntegralFlow.html", + "complexity": "(max_capacity + 1)^(2 * num_arcs)" + }, + { + "name": "ExactCoverBy3Sets", + "variant": {}, + "category": "set", + "doc_path": "models/set/struct.ExactCoverBy3Sets.html", + "complexity": "2^universe_size" + }, + { + "name": "Factoring", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.Factoring.html", + "complexity": "exp((m + n)^(1/3) * log(m + n)^(2/3))" + }, + { + "name": "FlowShopScheduling", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.FlowShopScheduling.html", + "complexity": "factorial(num_jobs)" + }, + { + "name": "GraphPartitioning", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.GraphPartitioning.html", + "complexity": "2^num_vertices" + }, + { + "name": "HamiltonianPath", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.HamiltonianPath.html", + "complexity": "1.657^num_vertices" + }, + { + "name": "ILP", + "variant": { + "variable": "bool" + }, + "category": "algebraic", + "doc_path": "models/algebraic/struct.ILP.html", + "complexity": "2^num_vars" + }, + { + "name": "ILP", + "variant": { + "variable": "i32" + }, + "category": "algebraic", + "doc_path": "models/algebraic/struct.ILP.html", + "complexity": "num_vars^num_vars" + }, + { + "name": "IsomorphicSpanningTree", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.IsomorphicSpanningTree.html", + "complexity": "factorial(num_vertices)" + }, + { + "name": "KColoring", + "variant": { + "graph": "SimpleGraph", + "k": "K2" + }, + "category": "graph", + "doc_path": "models/graph/struct.KColoring.html", + "complexity": "num_vertices + num_edges" + }, + { + "name": "KColoring", + "variant": { + "graph": "SimpleGraph", + "k": "K3" + }, + "category": "graph", + "doc_path": "models/graph/struct.KColoring.html", + "complexity": "1.3289^num_vertices" + }, + { + "name": "KColoring", + "variant": { + "graph": "SimpleGraph", + "k": "K4" + }, + "category": "graph", + "doc_path": "models/graph/struct.KColoring.html", + "complexity": "1.7159^num_vertices" + }, + { + "name": "KColoring", + "variant": { + "graph": "SimpleGraph", + "k": "K5" + }, + "category": "graph", + "doc_path": "models/graph/struct.KColoring.html", + "complexity": "2^num_vertices" + }, + { + "name": "KColoring", + "variant": { + "graph": "SimpleGraph", + "k": "KN" + }, + "category": "graph", + "doc_path": "models/graph/struct.KColoring.html", + "complexity": "2^num_vertices" + }, + { + "name": "KSatisfiability", + "variant": { + "k": "K2" + }, + "category": "formula", + "doc_path": "models/formula/struct.KSatisfiability.html", + "complexity": "num_variables + num_clauses" + }, + { + "name": "KSatisfiability", + "variant": { + "k": "K3" + }, + "category": "formula", + "doc_path": "models/formula/struct.KSatisfiability.html", + "complexity": "1.307^num_variables" + }, + { + "name": "KSatisfiability", + "variant": { + "k": "KN" + }, + "category": "formula", + "doc_path": "models/formula/struct.KSatisfiability.html", + "complexity": "2^num_variables" + }, + { + "name": "Knapsack", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.Knapsack.html", + "complexity": "2^(num_items / 2)" + }, + { + "name": "LengthBoundedDisjointPaths", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.LengthBoundedDisjointPaths.html", + "complexity": "2^(num_paths_required * num_vertices)" + }, + { + "name": "LongestCommonSubsequence", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.LongestCommonSubsequence.html", + "complexity": "2^min_string_length" + }, + { + "name": "MaxCut", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaxCut.html", + "complexity": "2^(2.372 * num_vertices / 3)" + }, + { + "name": "MaximalIS", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximalIS.html", + "complexity": "3^(num_vertices / 3)" + }, + { + "name": "MaximumClique", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumClique.html", + "complexity": "1.1996^num_vertices" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "KingsSubgraph", + "weight": "One" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "2^sqrt(num_vertices)" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "KingsSubgraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "2^sqrt(num_vertices)" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "SimpleGraph", + "weight": "One" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "1.1996^num_vertices" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "1.1996^num_vertices" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "TriangularSubgraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "2^sqrt(num_vertices)" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "UnitDiskGraph", + "weight": "One" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "2^sqrt(num_vertices)" + }, + { + "name": "MaximumIndependentSet", + "variant": { + "graph": "UnitDiskGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumIndependentSet.html", + "complexity": "2^sqrt(num_vertices)" + }, + { + "name": "MaximumMatching", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MaximumMatching.html", + "complexity": "num_vertices^3" + }, + { + "name": "MaximumSetPacking", + "variant": { + "weight": "One" + }, + "category": "set", + "doc_path": "models/set/struct.MaximumSetPacking.html", + "complexity": "2^num_sets" + }, + { + "name": "MaximumSetPacking", + "variant": { + "weight": "f64" + }, + "category": "set", + "doc_path": "models/set/struct.MaximumSetPacking.html", + "complexity": "2^num_sets" + }, + { + "name": "MaximumSetPacking", + "variant": { + "weight": "i32" + }, + "category": "set", + "doc_path": "models/set/struct.MaximumSetPacking.html", + "complexity": "2^num_sets" + }, + { + "name": "MinimumDominatingSet", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumDominatingSet.html", + "complexity": "1.4969^num_vertices" + }, + { + "name": "MinimumFeedbackArcSet", + "variant": { + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumFeedbackArcSet.html", + "complexity": "2^num_vertices" + }, + { + "name": "MinimumFeedbackVertexSet", + "variant": { + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumFeedbackVertexSet.html", + "complexity": "1.9977^num_vertices" + }, + { + "name": "MinimumMultiwayCut", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumMultiwayCut.html", + "complexity": "1.84^num_terminals * num_vertices^3" + }, + { + "name": "MinimumSetCovering", + "variant": { + "weight": "i32" + }, + "category": "set", + "doc_path": "models/set/struct.MinimumSetCovering.html", + "complexity": "2^num_sets" + }, + { + "name": "MinimumSumMulticenter", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumSumMulticenter.html", + "complexity": "2^num_vertices" + }, + { + "name": "MinimumTardinessSequencing", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.MinimumTardinessSequencing.html", + "complexity": "2^num_tasks" + }, + { + "name": "MinimumVertexCover", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MinimumVertexCover.html", + "complexity": "1.1996^num_vertices" + }, + { + "name": "MultipleChoiceBranching", + "variant": { + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.MultipleChoiceBranching.html", + "complexity": "2^num_arcs" + }, + { + "name": "OptimalLinearArrangement", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.OptimalLinearArrangement.html", + "complexity": "2^num_vertices" + }, + { + "name": "PaintShop", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.PaintShop.html", + "complexity": "2^num_cars" + }, + { + "name": "PartitionIntoTriangles", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.PartitionIntoTriangles.html", + "complexity": "2^num_vertices" + }, + { + "name": "QUBO", + "variant": { + "weight": "f64" + }, + "category": "algebraic", + "doc_path": "models/algebraic/struct.QUBO.html", + "complexity": "2^num_vars" + }, + { + "name": "RuralPostman", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.RuralPostman.html", + "complexity": "2^num_vertices * num_vertices^2" + }, + { + "name": "Satisfiability", + "variant": {}, + "category": "formula", + "doc_path": "models/formula/struct.Satisfiability.html", + "complexity": "2^num_variables" + }, + { + "name": "SequencingWithinIntervals", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.SequencingWithinIntervals.html", + "complexity": "2^num_tasks" + }, + { + "name": "SetBasis", + "variant": {}, + "category": "set", + "doc_path": "models/set/struct.SetBasis.html", + "complexity": "2^(basis_size * universe_size)" + }, + { + "name": "ShortestCommonSupersequence", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.ShortestCommonSupersequence.html", + "complexity": "alphabet_size ^ bound" + }, + { + "name": "SpinGlass", + "variant": { + "graph": "SimpleGraph", + "weight": "f64" + }, + "category": "graph", + "doc_path": "models/graph/struct.SpinGlass.html", + "complexity": "2^num_spins" + }, + { + "name": "SpinGlass", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.SpinGlass.html", + "complexity": "2^num_spins" + }, + { + "name": "SteinerTree", + "variant": { + "graph": "SimpleGraph", + "weight": "One" + }, + "category": "graph", + "doc_path": "models/graph/struct.SteinerTree.html", + "complexity": "3^num_terminals * num_vertices + 2^num_terminals * num_vertices^2" + }, + { + "name": "SteinerTree", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.SteinerTree.html", + "complexity": "3^num_terminals * num_vertices + 2^num_terminals * num_vertices^2" + }, + { + "name": "StrongConnectivityAugmentation", + "variant": { + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.StrongConnectivityAugmentation.html", + "complexity": "2^num_potential_arcs" + }, + { + "name": "SubgraphIsomorphism", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.SubgraphIsomorphism.html", + "complexity": "num_host_vertices ^ num_pattern_vertices" + }, + { + "name": "SubsetSum", + "variant": {}, + "category": "misc", + "doc_path": "models/misc/struct.SubsetSum.html", + "complexity": "2^(num_elements / 2)" + }, + { + "name": "TravelingSalesman", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "category": "graph", + "doc_path": "models/graph/struct.TravelingSalesman.html", + "complexity": "2^num_vertices" + }, + { + "name": "UndirectedTwoCommodityIntegralFlow", + "variant": {}, + "category": "graph", + "doc_path": "models/graph/struct.UndirectedTwoCommodityIntegralFlow.html", + "complexity": "5^num_edges" + } + ], + "edges": [ + { + "source": 5, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_items * num_items + num_items" + }, + { + "field": "num_constraints", + "formula": "2 * num_items" + } + ], + "doc_path": "rules/binpacking_ilp/index.html" + }, + { + "source": 7, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_variables + num_assignments" + }, + { + "field": "num_constraints", + "formula": "num_variables + num_assignments" + } + ], + "doc_path": "rules/circuit_ilp/index.html" + }, + { + "source": 7, + "target": 63, + "overhead": [ + { + "field": "num_spins", + "formula": "num_assignments" + }, + { + "field": "num_interactions", + "formula": "num_assignments" + } + ], + "doc_path": "rules/circuit_spinglass/index.html" + }, + { + "source": 12, + "target": 7, + "overhead": [ + { + "field": "num_variables", + "formula": "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second" + }, + { + "field": "num_assignments", + "formula": "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second" + } + ], + "doc_path": "rules/factoring_circuit/index.html" + }, + { + "source": 12, + "target": 17, + "overhead": [ + { + "field": "num_vars", + "formula": "num_bits_first * num_bits_second" + }, + { + "field": "num_constraints", + "formula": "num_bits_first * num_bits_second" + } + ], + "doc_path": "rules/factoring_ilp/index.html" + }, + { + "source": 16, + "target": 17, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars" + }, + { + "field": "num_constraints", + "formula": "num_constraints + num_vars" + } + ], + "doc_path": "rules/ilp_bool_ilp_i32/index.html" + }, + { + "source": 16, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars + num_constraints * num_vars" + } + ], + "doc_path": "rules/ilp_qubo/index.html" + }, + { + "source": 20, + "target": 23, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/kcoloring_casts/index.html" + }, + { + "source": 23, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices^2" + }, + { + "field": "num_constraints", + "formula": "num_vertices + num_vertices * num_edges" + } + ], + "doc_path": "rules/coloring_ilp/index.html" + }, + { + "source": 23, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices^2" + } + ], + "doc_path": "rules/coloring_qubo/index.html" + }, + { + "source": 24, + "target": 26, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars" + }, + { + "field": "num_clauses", + "formula": "num_clauses" + } + ], + "doc_path": "rules/ksatisfiability_casts/index.html" + }, + { + "source": 24, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars" + } + ], + "doc_path": "rules/ksatisfiability_qubo/index.html" + }, + { + "source": 25, + "target": 26, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars" + }, + { + "field": "num_clauses", + "formula": "num_clauses" + } + ], + "doc_path": "rules/ksatisfiability_casts/index.html" + }, + { + "source": 25, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars + num_clauses" + } + ], + "doc_path": "rules/ksatisfiability_qubo/index.html" + }, + { + "source": 25, + "target": 68, + "overhead": [ + { + "field": "num_elements", + "formula": "2 * num_vars + 2 * num_clauses" + } + ], + "doc_path": "rules/ksatisfiability_subsetsum/index.html" + }, + { + "source": 26, + "target": 58, + "overhead": [ + { + "field": "num_clauses", + "formula": "num_clauses" + }, + { + "field": "num_vars", + "formula": "num_vars" + }, + { + "field": "num_literals", + "formula": "num_literals" + } + ], + "doc_path": "rules/sat_ksat/index.html" + }, + { + "source": 27, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_items + num_slack_bits" + } + ], + "doc_path": "rules/knapsack_qubo/index.html" + }, + { + "source": 29, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_chars_first * num_chars_second" + }, + { + "field": "num_constraints", + "formula": "num_chars_first + num_chars_second + (num_chars_first * num_chars_second)^2" + } + ], + "doc_path": "rules/longestcommonsubsequence_ilp/index.html" + }, + { + "source": 30, + "target": 63, + "overhead": [ + { + "field": "num_spins", + "formula": "num_vertices" + }, + { + "field": "num_interactions", + "formula": "num_edges" + } + ], + "doc_path": "rules/spinglass_maxcut/index.html" + }, + { + "source": 32, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices" + }, + { + "field": "num_constraints", + "formula": "num_vertices^2" + } + ], + "doc_path": "rules/maximumclique_ilp/index.html" + }, + { + "source": 32, + "target": 36, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_vertices * (num_vertices + -1 * 1) * 2^-1 + -1 * num_edges" + } + ], + "doc_path": "rules/maximumclique_maximumindependentset/index.html" + }, + { + "source": 33, + "target": 34, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 33, + "target": 38, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 34, + "target": 39, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 35, + "target": 33, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices * num_vertices" + }, + { + "field": "num_edges", + "formula": "num_vertices * num_vertices" + } + ], + "doc_path": "rules/maximumindependentset_gridgraph/index.html" + }, + { + "source": 35, + "target": 36, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 35, + "target": 37, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices * num_vertices" + }, + { + "field": "num_edges", + "formula": "num_vertices * num_vertices" + } + ], + "doc_path": "rules/maximumindependentset_triangular/index.html" + }, + { + "source": 35, + "target": 41, + "overhead": [ + { + "field": "num_sets", + "formula": "num_vertices" + }, + { + "field": "universe_size", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, + { + "source": 36, + "target": 32, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_vertices * (num_vertices + -1 * 1) * 2^-1 + -1 * num_edges" + } + ], + "doc_path": "rules/maximumindependentset_maximumclique/index.html" + }, + { + "source": 36, + "target": 43, + "overhead": [ + { + "field": "num_sets", + "formula": "num_vertices" + }, + { + "field": "universe_size", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, + { + "source": 36, + "target": 51, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" + }, + { + "source": 37, + "target": 39, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 38, + "target": 35, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 38, + "target": 39, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 39, + "target": 36, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/maximumindependentset_casts/index.html" + }, + { + "source": 40, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_edges" + }, + { + "field": "num_constraints", + "formula": "num_vertices" + } + ], + "doc_path": "rules/maximummatching_ilp/index.html" + }, + { + "source": 40, + "target": 43, + "overhead": [ + { + "field": "num_sets", + "formula": "num_edges" + }, + { + "field": "universe_size", + "formula": "num_vertices" + } + ], + "doc_path": "rules/maximummatching_maximumsetpacking/index.html" + }, + { + "source": 41, + "target": 35, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_sets" + }, + { + "field": "num_edges", + "formula": "num_sets^2" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, + { + "source": 41, + "target": 43, + "overhead": [ + { + "field": "num_sets", + "formula": "num_sets" + }, + { + "field": "universe_size", + "formula": "universe_size" + } + ], + "doc_path": "rules/maximumsetpacking_casts/index.html" + }, + { + "source": 42, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_sets" + } + ], + "doc_path": "rules/maximumsetpacking_qubo/index.html" + }, + { + "source": 43, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_sets" + }, + { + "field": "num_constraints", + "formula": "universe_size" + } + ], + "doc_path": "rules/maximumsetpacking_ilp/index.html" + }, + { + "source": 43, + "target": 36, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_sets" + }, + { + "field": "num_edges", + "formula": "num_sets^2" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, + { + "source": 43, + "target": 42, + "overhead": [ + { + "field": "num_sets", + "formula": "num_sets" + }, + { + "field": "universe_size", + "formula": "universe_size" + } + ], + "doc_path": "rules/maximumsetpacking_casts/index.html" + }, + { + "source": 44, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices" + }, + { + "field": "num_constraints", + "formula": "num_vertices" + } + ], + "doc_path": "rules/minimumdominatingset_ilp/index.html" + }, + { + "source": 48, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_sets" + }, + { + "field": "num_constraints", + "formula": "universe_size" + } + ], + "doc_path": "rules/minimumsetcovering_ilp/index.html" + }, + { + "source": 51, + "target": 36, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vertices" + }, + { + "field": "num_edges", + "formula": "num_edges" + } + ], + "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" + }, + { + "source": 51, + "target": 48, + "overhead": [ + { + "field": "num_sets", + "formula": "num_vertices" + }, + { + "field": "universe_size", + "formula": "num_edges" + } + ], + "doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html" + }, + { + "source": 56, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vars^2" + }, + { + "field": "num_constraints", + "formula": "num_vars^2" + } + ], + "doc_path": "rules/qubo_ilp/index.html" + }, + { + "source": 56, + "target": 62, + "overhead": [ + { + "field": "num_spins", + "formula": "num_vars" + } + ], + "doc_path": "rules/spinglass_qubo/index.html" + }, + { + "source": 58, + "target": 7, + "overhead": [ + { + "field": "num_variables", + "formula": "num_vars + num_clauses + 1" + }, + { + "field": "num_assignments", + "formula": "num_clauses + 2" + } + ], + "doc_path": "rules/sat_circuitsat/index.html" + }, + { + "source": 58, + "target": 20, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_vars + num_literals" + }, + { + "field": "num_edges", + "formula": "num_vars + num_literals" + } + ], + "doc_path": "rules/sat_coloring/index.html" + }, + { + "source": 58, + "target": 25, + "overhead": [ + { + "field": "num_clauses", + "formula": "4 * num_clauses + num_literals" + }, + { + "field": "num_vars", + "formula": "num_vars + 3 * num_clauses + num_literals" + } + ], + "doc_path": "rules/sat_ksat/index.html" + }, + { + "source": 58, + "target": 35, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_literals" + }, + { + "field": "num_edges", + "formula": "num_literals^2" + } + ], + "doc_path": "rules/sat_maximumindependentset/index.html" + }, + { + "source": 58, + "target": 44, + "overhead": [ + { + "field": "num_vertices", + "formula": "3 * num_vars + num_clauses" + }, + { + "field": "num_edges", + "formula": "3 * num_vars + num_literals" + } + ], + "doc_path": "rules/sat_minimumdominatingset/index.html" + }, + { + "source": 62, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_spins" + } + ], + "doc_path": "rules/spinglass_qubo/index.html" + }, + { + "source": 63, + "target": 30, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_spins" + }, + { + "field": "num_edges", + "formula": "num_interactions" + } + ], + "doc_path": "rules/spinglass_maxcut/index.html" + }, + { + "source": 63, + "target": 62, + "overhead": [ + { + "field": "num_spins", + "formula": "num_spins" + }, + { + "field": "num_interactions", + "formula": "num_interactions" + } + ], + "doc_path": "rules/spinglass_casts/index.html" + }, + { + "source": 69, + "target": 16, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices^2 + 2 * num_vertices * num_edges" + }, + { + "field": "num_constraints", + "formula": "num_vertices^3 + -1 * 1 * num_vertices^2 + 2 * num_vertices + 4 * num_vertices * num_edges" + } + ], + "doc_path": "rules/travelingsalesman_ilp/index.html" + }, + { + "source": 69, + "target": 56, + "overhead": [ + { + "field": "num_vars", + "formula": "num_vertices^2" + } + ], + "doc_path": "rules/travelingsalesman_qubo/index.html" + } + ] +} \ No newline at end of file diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index 1d49ad69d..1a9037e1d 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -237,6 +237,7 @@ Flags by problem type: X3C (ExactCoverBy3Sets) --universe, --sets (3 elements each) SetBasis --universe, --sets, --k BicliqueCover --left, --right, --biedges, --k + BalancedCompleteBipartiteSubgraph --left, --right, --biedges, --k BiconnectivityAugmentation --graph, --potential-edges, --budget [--num-vertices] BMF --matrix (0/1), --rank SteinerTree --graph, --edge-weights, --terminals @@ -394,13 +395,13 @@ pub struct CreateArgs { /// Universe size for MinimumSetCovering #[arg(long)] pub universe: Option, - /// Bipartite graph edges for BicliqueCover (e.g., "0-0,0-1,1-2" for left-right pairs) + /// Bipartite graph edges for BicliqueCover / BalancedCompleteBipartiteSubgraph (e.g., "0-0,0-1,1-2" for left-right pairs) #[arg(long)] pub biedges: Option, - /// Left partition size for BicliqueCover + /// Left partition size for BicliqueCover / BalancedCompleteBipartiteSubgraph #[arg(long)] pub left: Option, - /// Right partition size for BicliqueCover + /// Right partition size for BicliqueCover / BalancedCompleteBipartiteSubgraph #[arg(long)] pub right: Option, /// Rank for BMF diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 8fb2bd823..973005907 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -279,6 +279,9 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { "MinimumSumMulticenter" => { "--graph 0-1,1-2,2-3 --weights 1,1,1,1 --edge-weights 1,1,1 --k 2" } + "BalancedCompleteBipartiteSubgraph" => { + "--left 4 --right 4 --biedges 0-0,0-1,0-2,1-0,1-1,1-2,2-0,2-1,2-2,3-0,3-1,3-3 --k 3" + } "PartitionIntoTriangles" => "--graph 0-1,1-2,0-2", "Factoring" => "--target 15 --m 4 --n 4", "MinimumMultiwayCut" => "--graph 0-1,1-2,2-3 --terminals 0,2 --edge-weights 1,1,1", @@ -371,7 +374,20 @@ fn print_problem_help(canonical: &str, graph_type: Option<&str>) -> Result<()> { } else if field.type_name == "DirectedGraph" { // DirectedGraph fields use --arcs, not --graph let hint = type_format_hint(&field.type_name, graph_type); - eprintln!(" --{:<16} {} ({})", flag_name, field.description, hint); + eprintln!(" --{:<16} {} ({})", "arcs", field.description, hint); + } else if field.type_name == "BipartiteGraph" { + eprintln!( + " --{:<16} {} ({})", + "left", "Vertices in the left partition", "integer" + ); + eprintln!( + " --{:<16} {} ({})", + "right", "Vertices in the right partition", "integer" + ); + eprintln!( + " --{:<16} {} ({})", + "biedges", "Bipartite edges as left-right pairs", "edge list: 0-0,0-1,1-2" + ); } else { let hint = help_flag_hint(canonical, &field.name, &field.type_name, graph_type); eprintln!( @@ -1102,26 +1118,27 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { // BicliqueCover "BicliqueCover" => { - let left = args.left.ok_or_else(|| { - anyhow::anyhow!( - "BicliqueCover requires --left, --right, --biedges, and --k\n\n\ - Usage: pred create BicliqueCover --left 2 --right 2 --biedges 0-0,0-1,1-1 --k 2" - ) - })?; - let right = args.right.ok_or_else(|| { - anyhow::anyhow!("BicliqueCover requires --right (right partition size)") - })?; - let k = args.k.ok_or_else(|| { - anyhow::anyhow!("BicliqueCover requires --k (number of bicliques)") - })?; - let edges_str = args.biedges.as_deref().ok_or_else(|| { - anyhow::anyhow!("BicliqueCover requires --biedges (e.g., 0-0,0-1,1-1)") - })?; - let edges = util::parse_edge_pairs(edges_str)?; - let graph = BipartiteGraph::new(left, right, edges); + let usage = "pred create BicliqueCover --left 2 --right 2 --biedges 0-0,0-1,1-1 --k 2"; + let (graph, k) = + parse_bipartite_problem_input(args, "BicliqueCover", "number of bicliques", usage)?; (ser(BicliqueCover::new(graph, k))?, resolved_variant.clone()) } + // BalancedCompleteBipartiteSubgraph + "BalancedCompleteBipartiteSubgraph" => { + let usage = "pred create BalancedCompleteBipartiteSubgraph --left 4 --right 4 --biedges 0-0,0-1,0-2,1-0,1-1,1-2,2-0,2-1,2-2,3-0,3-1,3-3 --k 3"; + let (graph, k) = parse_bipartite_problem_input( + args, + "BalancedCompleteBipartiteSubgraph", + "balanced biclique size", + usage, + )?; + ( + ser(BalancedCompleteBipartiteSubgraph::new(graph, k))?, + resolved_variant.clone(), + ) + } + // BMF "BMF" => { let matrix = parse_bool_matrix(args)?; @@ -1622,6 +1639,7 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { emit_problem_output(&output, out) } + /// Reject non-unit weights when the resolved variant uses `weight=One`. fn reject_nonunit_weights_for_one_variant( canonical: &str, @@ -1741,6 +1759,48 @@ fn variant_map(pairs: &[(&str, &str)]) -> BTreeMap { util::variant_map(pairs) } +fn parse_bipartite_problem_input( + args: &CreateArgs, + canonical: &str, + k_description: &str, + usage: &str, +) -> Result<(BipartiteGraph, usize)> { + let left = args.left.ok_or_else(|| { + anyhow::anyhow!( + "{canonical} requires --left, --right, --biedges, and --k\n\nUsage: {usage}" + ) + })?; + let right = args.right.ok_or_else(|| { + anyhow::anyhow!("{canonical} requires --right (right partition size)\n\nUsage: {usage}") + })?; + let k = args.k.ok_or_else(|| { + anyhow::anyhow!("{canonical} requires --k ({k_description})\n\nUsage: {usage}") + })?; + let edges_str = args.biedges.as_deref().ok_or_else(|| { + anyhow::anyhow!("{canonical} requires --biedges (e.g., 0-0,0-1,1-1)\n\nUsage: {usage}") + })?; + let edges = util::parse_edge_pairs(edges_str)?; + validate_bipartite_edges(canonical, left, right, &edges)?; + Ok((BipartiteGraph::new(left, right, edges), k)) +} + +fn validate_bipartite_edges( + canonical: &str, + left: usize, + right: usize, + edges: &[(usize, usize)], +) -> Result<()> { + for &(u, v) in edges { + if u >= left { + bail!("{canonical} edge {u}-{v} is out of bounds for left partition size {left}"); + } + if v >= right { + bail!("{canonical} edge {u}-{v} is out of bounds for right partition size {right}"); + } + } + Ok(()) +} + /// Parse `--graph` into a SimpleGraph, optionally preserving isolated vertices /// via `--num-vertices`. fn parse_graph(args: &CreateArgs) -> Result<(SimpleGraph, usize)> { @@ -2830,4 +2890,64 @@ mod tests { std::fs::remove_file(output_path).ok(); } + + #[test] + fn test_create_balanced_complete_bipartite_subgraph() { + use crate::dispatch::ProblemJsonOutput; + use problemreductions::models::graph::BalancedCompleteBipartiteSubgraph; + + let mut args = empty_args(); + args.problem = Some("BalancedCompleteBipartiteSubgraph".to_string()); + args.biedges = Some("0-0,0-1,0-2,1-0,1-1,1-2,2-0,2-1,2-2,3-0,3-1,3-3".to_string()); + args.left = Some(4); + args.right = Some(4); + args.k = Some(3); + args.graph = None; + + let output_path = + std::env::temp_dir().join(format!("bcbs-create-{}.json", std::process::id())); + let out = OutputConfig { + output: Some(output_path.clone()), + quiet: true, + json: false, + auto_json: false, + }; + + create(&args, &out).unwrap(); + + let json = std::fs::read_to_string(&output_path).unwrap(); + let created: ProblemJsonOutput = serde_json::from_str(&json).unwrap(); + assert_eq!(created.problem_type, "BalancedCompleteBipartiteSubgraph"); + assert!(created.variant.is_empty()); + + let problem: BalancedCompleteBipartiteSubgraph = + serde_json::from_value(created.data).unwrap(); + assert_eq!(problem.left_size(), 4); + assert_eq!(problem.right_size(), 4); + assert_eq!(problem.num_edges(), 12); + assert_eq!(problem.k(), 3); + + let _ = std::fs::remove_file(output_path); + } + + #[test] + fn test_create_balanced_complete_bipartite_subgraph_rejects_out_of_range_biedges() { + let mut args = empty_args(); + args.problem = Some("BalancedCompleteBipartiteSubgraph".to_string()); + args.biedges = Some("4-0".to_string()); + args.left = Some(4); + args.right = Some(4); + args.k = Some(3); + args.graph = None; + + let out = OutputConfig { + output: None, + quiet: true, + json: false, + auto_json: false, + }; + + let err = create(&args, &out).unwrap_err().to_string(); + assert!(err.contains("out of bounds for left partition size 4")); + } } diff --git a/problemreductions-cli/tests/cli_tests.rs b/problemreductions-cli/tests/cli_tests.rs index 6bbdc5f12..bd96bfeed 100644 --- a/problemreductions-cli/tests/cli_tests.rs +++ b/problemreductions-cli/tests/cli_tests.rs @@ -106,6 +106,49 @@ fn test_show_variant_info() { ); } +#[test] +fn test_show_balanced_complete_bipartite_subgraph_complexity() { + let output = pred() + .args(["show", "BalancedCompleteBipartiteSubgraph"]) + .output() + .unwrap(); + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!( + stdout.contains("1.3803^num_vertices"), + "expected updated complexity metadata, got: {stdout}" + ); +} + +#[test] +fn test_solve_balanced_complete_bipartite_subgraph_suggests_bruteforce() { + let tmp = std::env::temp_dir().join("pred_test_bcbs_problem.json"); + let create = pred() + .args([ + "create", + "--example", + "BalancedCompleteBipartiteSubgraph", + "--json", + ]) + .output() + .unwrap(); + assert!(create.status.success()); + std::fs::write(&tmp, create.stdout).unwrap(); + + let solve = pred() + .args(["solve", tmp.to_str().unwrap()]) + .output() + .unwrap(); + assert!(!solve.status.success()); + let stderr = String::from_utf8(solve.stderr).unwrap(); + assert!( + stderr.contains("--solver brute-force"), + "expected brute-force hint, got: {stderr}" + ); + + std::fs::remove_file(tmp).ok(); +} + #[test] fn test_path() { let output = pred().args(["path", "MIS", "QUBO"]).output().unwrap(); @@ -209,6 +252,22 @@ fn test_show_includes_fields() { assert!(stdout.contains("weights")); } +#[test] +fn test_create_balanced_complete_bipartite_subgraph_help_uses_bipartite_flags() { + let output = pred() + .args(["create", "BalancedCompleteBipartiteSubgraph"]) + .output() + .unwrap(); + assert!(!output.status.success()); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(stderr.contains("--left"), "stderr: {stderr}"); + assert!(stderr.contains("--right"), "stderr: {stderr}"); + assert!(stderr.contains("--biedges"), "stderr: {stderr}"); + assert!(!stderr.contains("--left-size"), "stderr: {stderr}"); + assert!(!stderr.contains("--right-size"), "stderr: {stderr}"); + assert!(!stderr.contains("--edges"), "stderr: {stderr}"); +} + #[test] fn test_list_json() { let tmp = std::env::temp_dir().join("pred_test_list.json"); diff --git a/src/example_db/fixtures/examples.json b/src/example_db/fixtures/examples.json index c13b45993..d8dd0fb6e 100644 --- a/src/example_db/fixtures/examples.json +++ b/src/example_db/fixtures/examples.json @@ -1,6 +1,7 @@ { "models": [ {"problem":"BMF","variant":{},"instance":{"k":2,"m":3,"matrix":[[true,true,false],[true,true,true],[false,true,true]],"n":3},"samples":[{"config":[1,0,1,1,0,1,1,1,0,0,1,1],"metric":{"Valid":0}}],"optimal":[{"config":[0,1,1,1,1,0,0,1,1,1,1,0],"metric":{"Valid":0}},{"config":[1,0,1,1,0,1,1,1,0,0,1,1],"metric":{"Valid":0}}]}, + {"problem":"BalancedCompleteBipartiteSubgraph","variant":{},"instance":{"graph":{"edges":[[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2],[3,0],[3,1],[3,3]],"left_size":4,"right_size":4},"k":3},"samples":[{"config":[1,1,1,0,1,1,1,0],"metric":true}],"optimal":[{"config":[1,1,1,0,1,1,1,0],"metric":true}]}, {"problem":"BicliqueCover","variant":{},"instance":{"graph":{"edges":[[0,0],[0,1],[1,1],[1,2]],"left_size":2,"right_size":3},"k":2},"samples":[{"config":[1,0,0,1,1,0,1,1,0,1],"metric":{"Valid":6}}],"optimal":[{"config":[0,1,0,1,0,1,0,1,0,1],"metric":{"Valid":5}},{"config":[1,0,1,0,1,0,1,0,1,0],"metric":{"Valid":5}}]}, {"problem":"BiconnectivityAugmentation","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"budget":4,"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[1,2,null],[2,3,null],[3,4,null],[4,5,null]],"node_holes":[],"nodes":[null,null,null,null,null,null]}},"potential_weights":[[0,2,1],[0,3,2],[0,4,3],[1,3,1],[1,4,2],[1,5,3],[2,4,1],[2,5,2],[3,5,1]]},"samples":[{"config":[1,0,0,1,0,0,1,0,1],"metric":true}],"optimal":[{"config":[0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,0,1,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,1],"metric":true},{"config":[1,0,0,0,0,1,0,0,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,0,0,1,0,0,0,1,0],"metric":true},{"config":[1,0,0,1,0,0,1,0,1],"metric":true}]}, {"problem":"BoundedComponentSpanningForest","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[1,2,null],[2,3,null],[3,4,null],[4,5,null],[5,6,null],[6,7,null],[0,7,null],[1,5,null],[2,6,null]],"node_holes":[],"nodes":[null,null,null,null,null,null,null,null]}},"max_components":3,"max_weight":6,"weights":[2,3,1,2,3,1,2,1]},"samples":[{"config":[0,0,1,1,1,2,2,0],"metric":true}],"optimal":[{"config":[0,0,0,1,1,1,2,2],"metric":true},{"config":[0,0,0,1,1,2,2,2],"metric":true},{"config":[0,0,0,2,2,1,1,1],"metric":true},{"config":[0,0,0,2,2,2,1,1],"metric":true},{"config":[0,0,1,1,1,0,2,2],"metric":true},{"config":[0,0,1,1,1,2,2,0],"metric":true},{"config":[0,0,1,1,1,2,2,2],"metric":true},{"config":[0,0,1,1,2,0,1,1],"metric":true},{"config":[0,0,1,1,2,1,1,0],"metric":true},{"config":[0,0,1,1,2,2,1,0],"metric":true},{"config":[0,0,1,1,2,2,1,1],"metric":true},{"config":[0,0,1,1,2,2,2,0],"metric":true},{"config":[0,0,1,2,2,0,1,1],"metric":true},{"config":[0,0,1,2,2,1,1,0],"metric":true},{"config":[0,0,1,2,2,1,1,1],"metric":true},{"config":[0,0,1,2,2,2,1,0],"metric":true},{"config":[0,0,1,2,2,2,1,1],"metric":true},{"config":[0,0,2,1,1,0,2,2],"metric":true},{"config":[0,0,2,1,1,1,2,0],"metric":true},{"config":[0,0,2,1,1,1,2,2],"metric":true},{"config":[0,0,2,1,1,2,2,0],"metric":true},{"config":[0,0,2,1,1,2,2,2],"metric":true},{"config":[0,0,2,2,1,0,2,2],"metric":true},{"config":[0,0,2,2,1,1,1,0],"metric":true},{"config":[0,0,2,2,1,1,2,0],"metric":true},{"config":[0,0,2,2,1,1,2,2],"metric":true},{"config":[0,0,2,2,1,2,2,0],"metric":true},{"config":[0,0,2,2,2,0,1,1],"metric":true},{"config":[0,0,2,2,2,1,1,0],"metric":true},{"config":[0,0,2,2,2,1,1,1],"metric":true},{"config":[0,1,0,2,2,1,0,0],"metric":true},{"config":[0,1,0,2,2,2,0,0],"metric":true},{"config":[0,1,1,1,2,0,0,0],"metric":true},{"config":[0,1,1,1,2,2,0,0],"metric":true},{"config":[0,1,1,1,2,2,2,0],"metric":true},{"config":[0,1,1,2,2,0,0,0],"metric":true},{"config":[0,1,1,2,2,1,0,0],"metric":true},{"config":[0,1,1,2,2,2,0,0],"metric":true},{"config":[0,1,1,2,2,2,1,0],"metric":true},{"config":[0,1,2,2,2,0,0,0],"metric":true},{"config":[0,1,2,2,2,1,0,0],"metric":true},{"config":[0,1,2,2,2,1,1,0],"metric":true},{"config":[0,2,0,1,1,1,0,0],"metric":true},{"config":[0,2,0,1,1,2,0,0],"metric":true},{"config":[0,2,1,1,1,0,0,0],"metric":true},{"config":[0,2,1,1,1,2,0,0],"metric":true},{"config":[0,2,1,1,1,2,2,0],"metric":true},{"config":[0,2,2,1,1,0,0,0],"metric":true},{"config":[0,2,2,1,1,1,0,0],"metric":true},{"config":[0,2,2,1,1,1,2,0],"metric":true},{"config":[0,2,2,1,1,2,0,0],"metric":true},{"config":[0,2,2,2,1,0,0,0],"metric":true},{"config":[0,2,2,2,1,1,0,0],"metric":true},{"config":[0,2,2,2,1,1,1,0],"metric":true},{"config":[1,0,0,0,2,1,1,1],"metric":true},{"config":[1,0,0,0,2,2,1,1],"metric":true},{"config":[1,0,0,0,2,2,2,1],"metric":true},{"config":[1,0,0,2,2,0,1,1],"metric":true},{"config":[1,0,0,2,2,1,1,1],"metric":true},{"config":[1,0,0,2,2,2,0,1],"metric":true},{"config":[1,0,0,2,2,2,1,1],"metric":true},{"config":[1,0,1,2,2,0,1,1],"metric":true},{"config":[1,0,1,2,2,2,1,1],"metric":true},{"config":[1,0,2,2,2,0,0,1],"metric":true},{"config":[1,0,2,2,2,0,1,1],"metric":true},{"config":[1,0,2,2,2,1,1,1],"metric":true},{"config":[1,1,0,0,0,1,2,2],"metric":true},{"config":[1,1,0,0,0,2,2,1],"metric":true},{"config":[1,1,0,0,0,2,2,2],"metric":true},{"config":[1,1,0,0,2,0,0,1],"metric":true},{"config":[1,1,0,0,2,1,0,0],"metric":true},{"config":[1,1,0,0,2,2,0,0],"metric":true},{"config":[1,1,0,0,2,2,0,1],"metric":true},{"config":[1,1,0,0,2,2,2,1],"metric":true},{"config":[1,1,0,2,2,0,0,0],"metric":true},{"config":[1,1,0,2,2,0,0,1],"metric":true},{"config":[1,1,0,2,2,1,0,0],"metric":true},{"config":[1,1,0,2,2,2,0,0],"metric":true},{"config":[1,1,0,2,2,2,0,1],"metric":true},{"config":[1,1,1,0,0,0,2,2],"metric":true},{"config":[1,1,1,0,0,2,2,2],"metric":true},{"config":[1,1,1,2,2,0,0,0],"metric":true},{"config":[1,1,1,2,2,2,0,0],"metric":true},{"config":[1,1,2,0,0,0,2,1],"metric":true},{"config":[1,1,2,0,0,0,2,2],"metric":true},{"config":[1,1,2,0,0,1,2,2],"metric":true},{"config":[1,1,2,0,0,2,2,1],"metric":true},{"config":[1,1,2,0,0,2,2,2],"metric":true},{"config":[1,1,2,2,0,0,0,1],"metric":true},{"config":[1,1,2,2,0,0,2,1],"metric":true},{"config":[1,1,2,2,0,0,2,2],"metric":true},{"config":[1,1,2,2,0,1,2,2],"metric":true},{"config":[1,1,2,2,0,2,2,1],"metric":true},{"config":[1,1,2,2,2,0,0,0],"metric":true},{"config":[1,1,2,2,2,0,0,1],"metric":true},{"config":[1,1,2,2,2,1,0,0],"metric":true},{"config":[1,2,0,0,0,1,1,1],"metric":true},{"config":[1,2,0,0,0,2,1,1],"metric":true},{"config":[1,2,0,0,0,2,2,1],"metric":true},{"config":[1,2,1,0,0,0,1,1],"metric":true},{"config":[1,2,1,0,0,2,1,1],"metric":true},{"config":[1,2,2,0,0,0,1,1],"metric":true},{"config":[1,2,2,0,0,0,2,1],"metric":true},{"config":[1,2,2,0,0,1,1,1],"metric":true},{"config":[1,2,2,0,0,2,1,1],"metric":true},{"config":[1,2,2,2,0,0,0,1],"metric":true},{"config":[1,2,2,2,0,0,1,1],"metric":true},{"config":[1,2,2,2,0,1,1,1],"metric":true},{"config":[2,0,0,0,1,1,1,2],"metric":true},{"config":[2,0,0,0,1,1,2,2],"metric":true},{"config":[2,0,0,0,1,2,2,2],"metric":true},{"config":[2,0,0,1,1,0,2,2],"metric":true},{"config":[2,0,0,1,1,1,0,2],"metric":true},{"config":[2,0,0,1,1,1,2,2],"metric":true},{"config":[2,0,0,1,1,2,2,2],"metric":true},{"config":[2,0,1,1,1,0,0,2],"metric":true},{"config":[2,0,1,1,1,0,2,2],"metric":true},{"config":[2,0,1,1,1,2,2,2],"metric":true},{"config":[2,0,2,1,1,0,2,2],"metric":true},{"config":[2,0,2,1,1,1,2,2],"metric":true},{"config":[2,1,0,0,0,1,1,2],"metric":true},{"config":[2,1,0,0,0,1,2,2],"metric":true},{"config":[2,1,0,0,0,2,2,2],"metric":true},{"config":[2,1,1,0,0,0,1,2],"metric":true},{"config":[2,1,1,0,0,0,2,2],"metric":true},{"config":[2,1,1,0,0,1,2,2],"metric":true},{"config":[2,1,1,0,0,2,2,2],"metric":true},{"config":[2,1,1,1,0,0,0,2],"metric":true},{"config":[2,1,1,1,0,0,2,2],"metric":true},{"config":[2,1,1,1,0,2,2,2],"metric":true},{"config":[2,1,2,0,0,0,2,2],"metric":true},{"config":[2,1,2,0,0,1,2,2],"metric":true},{"config":[2,2,0,0,0,1,1,1],"metric":true},{"config":[2,2,0,0,0,1,1,2],"metric":true},{"config":[2,2,0,0,0,2,1,1],"metric":true},{"config":[2,2,0,0,1,0,0,2],"metric":true},{"config":[2,2,0,0,1,1,0,0],"metric":true},{"config":[2,2,0,0,1,1,0,2],"metric":true},{"config":[2,2,0,0,1,1,1,2],"metric":true},{"config":[2,2,0,0,1,2,0,0],"metric":true},{"config":[2,2,0,1,1,0,0,0],"metric":true},{"config":[2,2,0,1,1,0,0,2],"metric":true},{"config":[2,2,0,1,1,1,0,0],"metric":true},{"config":[2,2,0,1,1,1,0,2],"metric":true},{"config":[2,2,0,1,1,2,0,0],"metric":true},{"config":[2,2,1,0,0,0,1,1],"metric":true},{"config":[2,2,1,0,0,0,1,2],"metric":true},{"config":[2,2,1,0,0,1,1,1],"metric":true},{"config":[2,2,1,0,0,1,1,2],"metric":true},{"config":[2,2,1,0,0,2,1,1],"metric":true},{"config":[2,2,1,1,0,0,0,2],"metric":true},{"config":[2,2,1,1,0,0,1,1],"metric":true},{"config":[2,2,1,1,0,0,1,2],"metric":true},{"config":[2,2,1,1,0,1,1,2],"metric":true},{"config":[2,2,1,1,0,2,1,1],"metric":true},{"config":[2,2,1,1,1,0,0,0],"metric":true},{"config":[2,2,1,1,1,0,0,2],"metric":true},{"config":[2,2,1,1,1,2,0,0],"metric":true},{"config":[2,2,2,0,0,0,1,1],"metric":true},{"config":[2,2,2,0,0,1,1,1],"metric":true},{"config":[2,2,2,1,1,0,0,0],"metric":true},{"config":[2,2,2,1,1,1,0,0],"metric":true}]}, @@ -55,7 +56,7 @@ {"source":{"problem":"KSatisfiability","variant":{"k":"K2"},"instance":{"clauses":[{"literals":[1,2]},{"literals":[-1,3]},{"literals":[-2,4]},{"literals":[-3,-4]}],"num_vars":4}},"target":{"problem":"QUBO","variant":{"weight":"f64"},"instance":{"matrix":[[0.0,1.0,-1.0,0.0],[0.0,0.0,0.0,-1.0],[0.0,0.0,0.0,1.0],[0.0,0.0,0.0,0.0]],"num_vars":4}},"solutions":[{"source_config":[0,1,0,1],"target_config":[0,1,0,1]}]}, {"source":{"problem":"KSatisfiability","variant":{"k":"K3"},"instance":{"clauses":[{"literals":[1,2,-3]},{"literals":[-1,3,4]},{"literals":[2,-4,5]},{"literals":[-2,3,-5]},{"literals":[1,-3,5]},{"literals":[-1,-2,4]},{"literals":[3,-4,-5]}],"num_vars":5}},"target":{"problem":"QUBO","variant":{"weight":"f64"},"instance":{"matrix":[[0.0,4.0,-4.0,0.0,0.0,4.0,-4.0,0.0,0.0,4.0,-4.0,0.0],[0.0,0.0,-2.0,-2.0,0.0,4.0,0.0,4.0,-4.0,0.0,-4.0,0.0],[0.0,0.0,2.0,-2.0,0.0,1.0,4.0,0.0,4.0,-4.0,0.0,4.0],[0.0,0.0,0.0,4.0,0.0,0.0,-1.0,-4.0,0.0,0.0,-1.0,-4.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,-1.0,1.0,-1.0,0.0,1.0],[0.0,0.0,0.0,0.0,0.0,-2.0,0.0,0.0,0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,3.0,0.0,0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,3.0,0.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,3.0,0.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,7.0,0.0],[0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,2.0]],"num_vars":12}},"solutions":[{"source_config":[1,1,1,1,1],"target_config":[1,1,1,1,1,0,0,0,0,0,1,0]}]}, {"source":{"problem":"KSatisfiability","variant":{"k":"K3"},"instance":{"clauses":[{"literals":[1,2,3]},{"literals":[-1,-2,3]}],"num_vars":3}},"target":{"problem":"SubsetSum","variant":{},"instance":{"sizes":["10010","10001","1010","1001","111","100","10","20","1","2"],"target":"11144"}},"solutions":[{"source_config":[0,0,1],"target_config":[0,1,0,1,1,0,1,1,1,0]}]}, - {"source":{"problem":"KSatisfiability","variant":{"k":"KN"},"instance":{"clauses":[{"literals":[1,-2,3]},{"literals":[-1,3,4]},{"literals":[2,-3,-4]}],"num_vars":4}},"target":{"problem":"Satisfiability","variant":{},"instance":{"clauses":[{"literals":[1,-2,3]},{"literals":[-1,3,4]},{"literals":[2,-3,-4]}],"num_vars":4}},"solutions":[{"source_config":[1,1,1,0],"target_config":[1,1,1,0]}]}, + {"source":{"problem":"KSatisfiability","variant":{"k":"KN"},"instance":{"clauses":[{"literals":[1,-2,3]},{"literals":[-1,3,4]},{"literals":[2,-3,-4]}],"num_vars":4}},"target":{"problem":"Satisfiability","variant":{},"instance":{"clauses":[{"literals":[1,-2,3]},{"literals":[-1,3,4]},{"literals":[2,-3,-4]}],"num_vars":4}},"solutions":[{"source_config":[1,1,1,1],"target_config":[1,1,1,1]}]}, {"source":{"problem":"Knapsack","variant":{},"instance":{"capacity":7,"values":[3,4,5,7],"weights":[2,3,4,5]}},"target":{"problem":"QUBO","variant":{"weight":"f64"},"instance":{"matrix":[[-483.0,240.0,320.0,400.0,80.0,160.0,320.0],[0.0,-664.0,480.0,600.0,120.0,240.0,480.0],[0.0,0.0,-805.0,800.0,160.0,320.0,640.0],[0.0,0.0,0.0,-907.0,200.0,400.0,800.0],[0.0,0.0,0.0,0.0,-260.0,80.0,160.0],[0.0,0.0,0.0,0.0,0.0,-480.0,320.0],[0.0,0.0,0.0,0.0,0.0,0.0,-800.0]],"num_vars":7}},"solutions":[{"source_config":[1,0,0,1],"target_config":[1,0,0,1,0,0,0]}]}, {"source":{"problem":"LongestCommonSubsequence","variant":{},"instance":{"strings":[[65,66,65,67],[66,65,67,65]]}},"target":{"problem":"ILP","variant":{"variable":"bool"},"instance":{"constraints":[{"cmp":"Le","rhs":1.0,"terms":[[0,1.0],[1,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[2,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[3,1.0],[4,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[5,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[2,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[0,1.0],[3,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[5,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[1,1.0],[4,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[0,1.0],[2,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[1,1.0],[2,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[1,1.0],[3,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[1,1.0],[5,1.0]]},{"cmp":"Le","rhs":1.0,"terms":[[4,1.0],[5,1.0]]}],"num_vars":6,"objective":[[0,1.0],[1,1.0],[2,1.0],[3,1.0],[4,1.0],[5,1.0]],"sense":"Maximize"}},"solutions":[{"source_config":[0,1,1,1],"target_config":[0,0,1,1,0,1]}]}, {"source":{"problem":"MaxCut","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"edge_weights":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[0,4,null],[0,5,null],[1,2,null],[1,6,null],[2,3,null],[2,7,null],[3,4,null],[3,8,null],[4,9,null],[5,7,null],[5,8,null],[6,8,null],[6,9,null],[7,9,null]],"node_holes":[],"nodes":[null,null,null,null,null,null,null,null,null,null]}}}},"target":{"problem":"SpinGlass","variant":{"graph":"SimpleGraph","weight":"i32"},"instance":{"couplings":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"fields":[0,0,0,0,0,0,0,0,0,0],"graph":{"inner":{"edge_property":"undirected","edges":[[0,1,null],[0,4,null],[0,5,null],[1,2,null],[1,6,null],[2,3,null],[2,7,null],[3,4,null],[3,8,null],[4,9,null],[5,7,null],[5,8,null],[6,8,null],[6,9,null],[7,9,null]],"node_holes":[],"nodes":[null,null,null,null,null,null,null,null,null,null]}}}},"solutions":[{"source_config":[1,0,1,0,0,0,0,0,1,1],"target_config":[1,0,1,0,0,0,0,0,1,1]}]}, diff --git a/src/lib.rs b/src/lib.rs index 563f63880..68b747161 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,10 +45,10 @@ pub mod prelude { pub use crate::models::algebraic::{BMF, QUBO}; pub use crate::models::formula::{CNFClause, CircuitSAT, KSatisfiability, Satisfiability}; pub use crate::models::graph::{ - BicliqueCover, BiconnectivityAugmentation, BoundedComponentSpanningForest, - DirectedTwoCommodityIntegralFlow, GraphPartitioning, HamiltonianPath, - IsomorphicSpanningTree, LengthBoundedDisjointPaths, SpinGlass, SteinerTree, - StrongConnectivityAugmentation, SubgraphIsomorphism, + BalancedCompleteBipartiteSubgraph, BicliqueCover, BiconnectivityAugmentation, + BoundedComponentSpanningForest, DirectedTwoCommodityIntegralFlow, GraphPartitioning, + HamiltonianPath, IsomorphicSpanningTree, LengthBoundedDisjointPaths, SpinGlass, + SteinerTree, StrongConnectivityAugmentation, SubgraphIsomorphism, }; pub use crate::models::graph::{ KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, diff --git a/src/models/graph/balanced_complete_bipartite_subgraph.rs b/src/models/graph/balanced_complete_bipartite_subgraph.rs new file mode 100644 index 000000000..dd648f847 --- /dev/null +++ b/src/models/graph/balanced_complete_bipartite_subgraph.rs @@ -0,0 +1,187 @@ +use crate::registry::{FieldInfo, ProblemSchemaEntry}; +use crate::topology::BipartiteGraph; +use crate::traits::{Problem, SatisfactionProblem}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +inventory::submit! { + ProblemSchemaEntry { + name: "BalancedCompleteBipartiteSubgraph", + display_name: "Balanced Complete Bipartite Subgraph", + aliases: &[], + dimensions: &[], + module_path: module_path!(), + description: "Decide whether a bipartite graph contains a K_{k,k} subgraph", + fields: &[ + FieldInfo { name: "graph", type_name: "BipartiteGraph", description: "The bipartite graph G = (A, B, E)" }, + FieldInfo { name: "k", type_name: "usize", description: "Balanced biclique size" }, + ], + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(from = "BalancedCompleteBipartiteSubgraphRepr")] +pub struct BalancedCompleteBipartiteSubgraph { + graph: BipartiteGraph, + k: usize, + #[serde(skip)] + edge_lookup: HashSet<(usize, usize)>, +} + +impl BalancedCompleteBipartiteSubgraph { + pub fn new(graph: BipartiteGraph, k: usize) -> Self { + let edge_lookup = Self::build_edge_lookup(&graph); + Self { + graph, + k, + edge_lookup, + } + } + + pub fn graph(&self) -> &BipartiteGraph { + &self.graph + } + + pub fn left_size(&self) -> usize { + self.graph.left_size() + } + + pub fn right_size(&self) -> usize { + self.graph.right_size() + } + + pub fn num_vertices(&self) -> usize { + self.left_size() + self.right_size() + } + + pub fn num_edges(&self) -> usize { + self.graph.left_edges().len() + } + + pub fn k(&self) -> usize { + self.k + } + + fn build_edge_lookup(graph: &BipartiteGraph) -> HashSet<(usize, usize)> { + graph.left_edges().iter().copied().collect() + } + + fn selected_vertices(&self, config: &[usize]) -> Option<(Vec, Vec)> { + if config.len() != self.num_vertices() { + return None; + } + + let mut selected_left = Vec::new(); + let mut selected_right = Vec::new(); + + for (index, &value) in config.iter().enumerate() { + match value { + 0 => {} + 1 => { + if index < self.left_size() { + selected_left.push(index); + } else { + selected_right.push(index - self.left_size()); + } + } + _ => return None, + } + } + + Some((selected_left, selected_right)) + } + + fn has_selected_edge(&self, left: usize, right: usize) -> bool { + self.edge_lookup.contains(&(left, right)) + } + + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.evaluate(config) + } +} + +impl Problem for BalancedCompleteBipartiteSubgraph { + const NAME: &'static str = "BalancedCompleteBipartiteSubgraph"; + type Metric = bool; + + fn dims(&self) -> Vec { + vec![2; self.num_vertices()] + } + + fn evaluate(&self, config: &[usize]) -> bool { + let Some((selected_left, selected_right)) = self.selected_vertices(config) else { + return false; + }; + + if selected_left.len() != self.k || selected_right.len() != self.k { + return false; + } + + selected_left.iter().all(|&left| { + selected_right + .iter() + .all(|&right| self.has_selected_edge(left, right)) + }) + } + + fn variant() -> Vec<(&'static str, &'static str)> { + crate::variant_params![] + } +} + +impl SatisfactionProblem for BalancedCompleteBipartiteSubgraph {} + +#[derive(Deserialize)] +struct BalancedCompleteBipartiteSubgraphRepr { + graph: BipartiteGraph, + k: usize, +} + +impl From for BalancedCompleteBipartiteSubgraph { + fn from(repr: BalancedCompleteBipartiteSubgraphRepr) -> Self { + Self::new(repr.graph, repr.k) + } +} + +crate::declare_variants! { + default sat BalancedCompleteBipartiteSubgraph => "1.3803^num_vertices", +} + +#[cfg(feature = "example-db")] +pub(crate) fn canonical_model_example_specs() -> Vec { + vec![crate::example_db::specs::ModelExampleSpec { + id: "balanced_complete_bipartite_subgraph", + build: || { + let problem = BalancedCompleteBipartiteSubgraph::new( + BipartiteGraph::new( + 4, + 4, + vec![ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 1), + (1, 2), + (2, 0), + (2, 1), + (2, 2), + (3, 0), + (3, 1), + (3, 3), + ], + ), + 3, + ); + + crate::example_db::specs::satisfaction_example( + problem, + vec![vec![1, 1, 1, 0, 1, 1, 1, 0]], + ) + }, + }] +} + +#[cfg(test)] +#[path = "../../unit_tests/models/graph/balanced_complete_bipartite_subgraph.rs"] +mod tests; diff --git a/src/models/graph/mod.rs b/src/models/graph/mod.rs index 8816f0dc0..b8da19aa7 100644 --- a/src/models/graph/mod.rs +++ b/src/models/graph/mod.rs @@ -18,6 +18,7 @@ //! - [`MinimumMultiwayCut`]: Minimum weight multiway cut //! - [`HamiltonianPath`]: Hamiltonian path (simple path visiting every vertex) //! - [`BicliqueCover`]: Biclique cover on bipartite graphs +//! - [`BalancedCompleteBipartiteSubgraph`]: Balanced biclique decision problem //! - [`BiconnectivityAugmentation`]: Biconnectivity augmentation with weighted potential edges //! - [`BoundedComponentSpanningForest`]: Partition vertices into bounded-weight connected components //! - [`OptimalLinearArrangement`]: Optimal linear arrangement (total edge length at most K) @@ -32,6 +33,7 @@ //! - [`UndirectedTwoCommodityIntegralFlow`]: Two-commodity integral flow on undirected graphs //! - [`StrongConnectivityAugmentation`]: Strong connectivity augmentation with weighted candidate arcs +pub(crate) mod balanced_complete_bipartite_subgraph; pub(crate) mod biclique_cover; pub(crate) mod biconnectivity_augmentation; pub(crate) mod bounded_component_spanning_forest; @@ -63,6 +65,7 @@ pub(crate) mod subgraph_isomorphism; pub(crate) mod traveling_salesman; pub(crate) mod undirected_two_commodity_integral_flow; +pub use balanced_complete_bipartite_subgraph::BalancedCompleteBipartiteSubgraph; pub use biclique_cover::BicliqueCover; pub use biconnectivity_augmentation::BiconnectivityAugmentation; pub use bounded_component_spanning_forest::BoundedComponentSpanningForest; @@ -115,6 +118,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec BipartiteGraph { + BipartiteGraph::new( + 4, + 4, + vec![ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 1), + (1, 3), + (2, 0), + (2, 2), + (2, 3), + (3, 1), + ], + ) +} + +fn issue_instance_2_graph() -> BipartiteGraph { + BipartiteGraph::new( + 4, + 4, + vec![ + (0, 0), + (0, 1), + (0, 2), + (1, 0), + (1, 1), + (1, 2), + (2, 0), + (2, 1), + (2, 2), + (3, 0), + (3, 1), + (3, 3), + ], + ) +} + +fn issue_instance_2_witness() -> Vec { + vec![1, 1, 1, 0, 1, 1, 1, 0] +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_creation() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 2); + + assert_eq!(problem.left_size(), 4); + assert_eq!(problem.right_size(), 4); + assert_eq!(problem.num_vertices(), 8); + assert_eq!(problem.num_edges(), 10); + assert_eq!(problem.k(), 2); + assert_eq!(problem.dims(), vec![2; 8]); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_evaluation_yes_instance() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 2); + + assert!(problem.evaluate(&[1, 1, 0, 0, 1, 1, 0, 0])); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_evaluation_no_instance() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 3); + + assert!(!problem.evaluate(&[1, 1, 1, 0, 1, 1, 1, 0])); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_invalid_pairing() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 2); + + assert!(!problem.evaluate(&[1, 1, 0, 0, 1, 0, 1, 0])); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_edge_lookup() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 2); + + assert!(problem.has_selected_edge(0, 0)); + assert!(problem.has_selected_edge(1, 3)); + assert!(!problem.has_selected_edge(3, 3)); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_rejects_invalid_configs() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 2); + + assert!(!problem.evaluate(&[1, 1, 0, 0, 1, 1, 0])); + assert!(!problem.evaluate(&[1, 2, 0, 0, 1, 1, 0, 0])); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_solver_yes_instance() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_2_graph(), 3); + let solver = BruteForce::new(); + + let solution = solver.find_satisfying(&problem); + assert!(solution.is_some()); + assert!(problem.evaluate(&solution.unwrap())); + + let all = solver.find_all_satisfying(&problem); + assert_eq!(all, vec![issue_instance_2_witness()]); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_solver_no_instance() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_1_graph(), 3); + let solver = BruteForce::new(); + + assert!(solver.find_satisfying(&problem).is_none()); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_serialization() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_2_graph(), 3); + let witness = issue_instance_2_witness(); + + let json = serde_json::to_string(&problem).unwrap(); + let deserialized: BalancedCompleteBipartiteSubgraph = serde_json::from_str(&json).unwrap(); + + assert_eq!(deserialized.left_size(), 4); + assert_eq!(deserialized.right_size(), 4); + assert_eq!(deserialized.num_edges(), 12); + assert_eq!( + deserialized.graph().left_edges(), + problem.graph().left_edges() + ); + assert_eq!(deserialized.k(), 3); + assert!(deserialized.evaluate(&witness)); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_is_valid_solution() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_2_graph(), 3); + let yes_config = issue_instance_2_witness(); + let no_config = vec![1, 1, 0, 1, 1, 1, 0, 0]; + + assert!(problem.is_valid_solution(&yes_config)); + assert!(!problem.is_valid_solution(&no_config)); + assert!(!problem.is_valid_solution(&[1, 1, 1])); +} + +#[test] +fn test_balanced_complete_bipartite_subgraph_paper_example() { + let problem = BalancedCompleteBipartiteSubgraph::new(issue_instance_2_graph(), 3); + let witness = issue_instance_2_witness(); + let solver = BruteForce::new(); + + assert!(problem.evaluate(&witness)); + assert_eq!(solver.find_all_satisfying(&problem), vec![witness]); +} diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index fb66a61d6..5c50c3745 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -80,6 +80,13 @@ fn test_all_problems_implement_trait_correctly() { &BicliqueCover::new(BipartiteGraph::new(2, 2, vec![(0, 0)]), 1), "BicliqueCover", ); + check_problem_trait( + &BalancedCompleteBipartiteSubgraph::new( + BipartiteGraph::new(2, 2, vec![(0, 0), (0, 1), (1, 0), (1, 1)]), + 2, + ), + "BalancedCompleteBipartiteSubgraph", + ); check_problem_trait(&Factoring::new(6, 2, 2), "Factoring"); let circuit = Circuit::new(vec![Assignment::new(