From 88ab7774a842311c129e1dc51f0d8ce1dbf64854 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 14:09:59 +0000 Subject: [PATCH 1/6] feat: add MaximumIndependentSet to MaximumClique reduction (#165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement Karp's classical complement graph reduction: an independent set in G is a clique in the complement graph Ḡ. Includes unit tests, example, and paper documentation. Co-Authored-By: Claude Opus 4.6 --- ...aximumindependentset_to_maximumclique.json | 133 ++++++++++++++++++ ...ndependentset_to_maximumclique.result.json | 20 +++ docs/paper/reductions.typ | 21 +++ ..._maximumindependentset_to_maximumclique.rs | 107 ++++++++++++++ .../maximumindependentset_maximumclique.rs | 66 +++++++++ src/rules/mod.rs | 1 + .../maximumindependentset_maximumclique.rs | 90 ++++++++++++ tests/suites/examples.rs | 5 + 8 files changed, 443 insertions(+) create mode 100644 docs/paper/examples/maximumindependentset_to_maximumclique.json create mode 100644 docs/paper/examples/maximumindependentset_to_maximumclique.result.json create mode 100644 examples/reduction_maximumindependentset_to_maximumclique.rs create mode 100644 src/rules/maximumindependentset_maximumclique.rs create mode 100644 src/unit_tests/rules/maximumindependentset_maximumclique.rs diff --git a/docs/paper/examples/maximumindependentset_to_maximumclique.json b/docs/paper/examples/maximumindependentset_to_maximumclique.json new file mode 100644 index 000000000..352ddeb68 --- /dev/null +++ b/docs/paper/examples/maximumindependentset_to_maximumclique.json @@ -0,0 +1,133 @@ +{ + "source": { + "problem": "MaximumIndependentSet", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "instance": { + "edges": [ + [ + 0, + 1 + ], + [ + 1, + 2 + ], + [ + 2, + 3 + ], + [ + 3, + 4 + ] + ], + "num_edges": 4, + "num_vertices": 5 + } + }, + "target": { + "problem": "MaximumClique", + "variant": { + "graph": "SimpleGraph", + "weight": "i32" + }, + "instance": { + "edges": [ + [ + 0, + 2 + ], + [ + 0, + 3 + ], + [ + 0, + 4 + ], + [ + 1, + 3 + ], + [ + 1, + 4 + ], + [ + 2, + 4 + ] + ], + "num_edges": 6, + "num_vertices": 5 + } + }, + "overhead": [ + { + "field": "num_vertices", + "expr": { + "Var": "num_vertices" + }, + "formula": "num_vertices" + }, + { + "field": "num_edges", + "expr": { + "Add": [ + { + "Mul": [ + { + "Mul": [ + { + "Var": "num_vertices" + }, + { + "Add": [ + { + "Var": "num_vertices" + }, + { + "Mul": [ + { + "Const": -1.0 + }, + { + "Const": 1.0 + } + ] + } + ] + } + ] + }, + { + "Pow": [ + { + "Const": 2.0 + }, + { + "Const": -1.0 + } + ] + } + ] + }, + { + "Mul": [ + { + "Const": -1.0 + }, + { + "Var": "num_edges" + } + ] + } + ] + }, + "formula": "num_vertices * (num_vertices + -1 * 1) * 2^-1 + -1 * num_edges" + } + ] +} \ No newline at end of file diff --git a/docs/paper/examples/maximumindependentset_to_maximumclique.result.json b/docs/paper/examples/maximumindependentset_to_maximumclique.result.json new file mode 100644 index 000000000..7e1d1a96e --- /dev/null +++ b/docs/paper/examples/maximumindependentset_to_maximumclique.result.json @@ -0,0 +1,20 @@ +{ + "solutions": [ + { + "source_config": [ + 1, + 0, + 1, + 0, + 1 + ], + "target_config": [ + 1, + 0, + 1, + 0, + 1 + ] + } + ] +} \ No newline at end of file diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index 915878827..055af3c94 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -950,6 +950,27 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead _Solution extraction._ For VC solution $C$, return $S = V backslash C$, i.e.\ flip each variable: $s_v = 1 - c_v$. ] +#let mis_clique = load-example("maximumindependentset_to_maximumclique") +#let mis_clique_r = load-results("maximumindependentset_to_maximumclique") +#let mis_clique_sol = mis_clique_r.solutions.at(0) +#reduction-rule("MaximumIndependentSet", "MaximumClique", + example: true, + example-caption: [Path graph $P_5$: IS $arrow.r$ Clique via complement], + extra: [ + Source IS: $S = {#mis_clique_sol.source_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => str(i)).join(", ")}$ (size #mis_clique_sol.source_config.filter(x => x == 1).len()) #h(1em) + Target Clique: $C = {#mis_clique_sol.target_config.enumerate().filter(((i, x)) => x == 1).map(((i, x)) => str(i)).join(", ")}$ (size #mis_clique_sol.target_config.filter(x => x == 1).len()) \ + Source $|E| = #mis_clique.source.instance.num_edges$, complement $|overline(E)| = #mis_clique.target.instance.num_edges$ #sym.checkmark + ], +)[ + An independent set in $G$ is exactly a clique in the complement graph $overline(G)$: vertices with no edges between them in $G$ are pairwise adjacent in $overline(G)$. Both problems maximize total vertex weight, so optimal values are preserved. This is Karp's classical complement graph reduction. +][ + _Construction._ Given IS instance $(G = (V, E), bold(w))$, build $overline(G) = (V, overline(E))$ where $overline(E) = {(u, v) : u != v, (u, v) in.not E}$. Create MaxClique instance $(overline(G), bold(w))$ with the same weights. Variables correspond one-to-one: vertex $v$ in the source maps to vertex $v$ in the target. + + _Correctness._ ($arrow.r.double$) If $S$ is independent in $G$, then for any $u, v in S$, $(u, v) in.not E$, so $(u, v) in overline(E)$ — all pairs in $S$ are adjacent in $overline(G)$, making $S$ a clique. ($arrow.l.double$) If $C$ is a clique in $overline(G)$, then for any $u, v in C$, $(u, v) in overline(E)$, so $(u, v) in.not E$ — no pair in $C$ is adjacent in $G$, making $C$ independent. Weight sums are identical, so optimality is preserved. + + _Solution extraction._ For clique solution $C$ in $overline(G)$, return IS $= C$ (identity mapping: $s_v = c_v$). +] + #reduction-rule("MaximumIndependentSet", "MaximumSetPacking")[ The key insight is that two vertices are adjacent if and only if they share an edge. By representing each vertex $v$ as the set of its incident edges $S_v$, adjacency becomes set overlap: $S_u inter S_v != emptyset$ iff $(u,v) in E$. Thus an independent set (no two adjacent) maps exactly to a packing (no two overlapping). ][ diff --git a/examples/reduction_maximumindependentset_to_maximumclique.rs b/examples/reduction_maximumindependentset_to_maximumclique.rs new file mode 100644 index 000000000..5f858b555 --- /dev/null +++ b/examples/reduction_maximumindependentset_to_maximumclique.rs @@ -0,0 +1,107 @@ +// # Independent Set to Clique Reduction +// +// ## Mathematical Equivalence +// S is an independent set in G iff S is a clique in the complement graph Ḡ. +// The reduction builds Ḡ by taking edges not in G. Solution extraction is +// identity: the same vertex set works for both problems. +// +// ## This Example +// - Instance: Path graph P5 (5 vertices, 4 edges) +// - Source MIS: max size 3 (e.g., {0, 2, 4}) +// - Target MaxClique on complement: max clique size 3 +// +// ## Output +// Exports `docs/paper/examples/maximumindependentset_to_maximumclique.json` and `.result.json`. + +use problemreductions::export::*; +use problemreductions::prelude::*; +use problemreductions::topology::{Graph, SimpleGraph}; + +pub fn run() { + // Path graph: 0-1-2-3-4 + let source = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + vec![1i32; 5], + ); + + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + println!("\n=== Problem Transformation ==="); + println!( + "Source: MaximumIndependentSet with {} vertices, {} edges", + source.graph().num_vertices(), + source.graph().num_edges() + ); + println!( + "Target: MaximumClique with {} vertices, {} edges (complement graph)", + target.num_vertices(), + target.num_edges() + ); + + let solver = BruteForce::new(); + let target_solutions = solver.find_all_best(target); + println!("\n=== Solution ==="); + println!("Target solutions found: {}", target_solutions.len()); + + let mut solutions = Vec::new(); + for target_sol in &target_solutions { + let source_sol = reduction.extract_solution(target_sol); + let size = source.evaluate(&source_sol); + assert!(size.is_valid()); + solutions.push(SolutionPair { + source_config: source_sol.clone(), + target_config: target_sol.clone(), + }); + } + + let source_sol = reduction.extract_solution(&target_solutions[0]); + println!("Source IS solution: {:?}", source_sol); + let size = source.evaluate(&source_sol); + println!("Solution size: {:?}", size); + assert!(size.is_valid()); + println!("\nReduction verified successfully"); + + // Export JSON + let source_edges = source.graph().edges(); + let target_edges = target.graph().edges(); + let source_variant = variant_to_map(MaximumIndependentSet::::variant()); + let target_variant = variant_to_map(MaximumClique::::variant()); + let overhead = lookup_overhead( + "MaximumIndependentSet", + &source_variant, + "MaximumClique", + &target_variant, + ) + .expect("MaximumIndependentSet -> MaximumClique overhead not found"); + + let data = ReductionData { + source: ProblemSide { + problem: MaximumIndependentSet::::NAME.to_string(), + variant: source_variant, + instance: serde_json::json!({ + "num_vertices": source.graph().num_vertices(), + "num_edges": source.graph().num_edges(), + "edges": source_edges, + }), + }, + target: ProblemSide { + problem: MaximumClique::::NAME.to_string(), + variant: target_variant, + instance: serde_json::json!({ + "num_vertices": target.num_vertices(), + "num_edges": target.num_edges(), + "edges": target_edges, + }), + }, + overhead: overhead_to_json(&overhead), + }; + + let results = ResultData { solutions }; + let name = "maximumindependentset_to_maximumclique"; + write_example(name, &data, &results); +} + +fn main() { + run() +} diff --git a/src/rules/maximumindependentset_maximumclique.rs b/src/rules/maximumindependentset_maximumclique.rs new file mode 100644 index 000000000..763b9d223 --- /dev/null +++ b/src/rules/maximumindependentset_maximumclique.rs @@ -0,0 +1,66 @@ +//! Reduction from MaximumIndependentSet to MaximumClique via complement graph. +//! +//! An independent set in G corresponds to a clique in the complement graph Ḡ. +//! This is Karp's classical complement graph reduction. + +use crate::models::graph::{MaximumClique, MaximumIndependentSet}; +use crate::reduction; +use crate::rules::traits::{ReduceTo, ReductionResult}; +use crate::topology::{Graph, SimpleGraph}; +use crate::types::WeightElement; + +/// Result of reducing MaximumIndependentSet to MaximumClique. +#[derive(Debug, Clone)] +pub struct ReductionISToClique { + target: MaximumClique, +} + +impl ReductionResult for ReductionISToClique +where + W: WeightElement + crate::variant::VariantParam, +{ + type Source = MaximumIndependentSet; + type Target = MaximumClique; + + fn target_problem(&self) -> &Self::Target { + &self.target + } + + /// Solution extraction: identity mapping. + /// A vertex selected in the clique (target) is also selected in the independent set (source). + fn extract_solution(&self, target_solution: &[usize]) -> Vec { + target_solution.to_vec() + } +} + +#[reduction( + overhead = { + num_vertices = "num_vertices", + num_edges = "num_vertices * (num_vertices - 1) / 2 - num_edges", + } +)] +impl ReduceTo> for MaximumIndependentSet { + type Result = ReductionISToClique; + + fn reduce_to(&self) -> Self::Result { + let n = self.graph().num_vertices(); + // Build complement graph edges + let mut complement_edges = Vec::new(); + for u in 0..n { + for v in (u + 1)..n { + if !self.graph().has_edge(u, v) { + complement_edges.push((u, v)); + } + } + } + let target = MaximumClique::new( + SimpleGraph::new(n, complement_edges), + self.weights().to_vec(), + ); + ReductionISToClique { target } + } +} + +#[cfg(test)] +#[path = "../unit_tests/rules/maximumindependentset_maximumclique.rs"] +mod tests; diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 80bdb735b..1f9239667 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -15,6 +15,7 @@ mod ksatisfiability_casts; mod ksatisfiability_qubo; mod maximumindependentset_casts; mod maximumindependentset_gridgraph; +mod maximumindependentset_maximumclique; mod maximumindependentset_maximumsetpacking; mod maximumindependentset_triangular; mod maximummatching_maximumsetpacking; diff --git a/src/unit_tests/rules/maximumindependentset_maximumclique.rs b/src/unit_tests/rules/maximumindependentset_maximumclique.rs new file mode 100644 index 000000000..1be4efe1e --- /dev/null +++ b/src/unit_tests/rules/maximumindependentset_maximumclique.rs @@ -0,0 +1,90 @@ +use super::*; +use crate::solvers::BruteForce; +use crate::traits::Problem; +use std::collections::HashSet; + +#[test] +fn test_maximumindependentset_to_maximumclique_closed_loop() { + // Path graph: 0-1-2-3-4 + let source = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + vec![1i32; 5], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + // Complement of path graph should have n*(n-1)/2 - m = 10 - 4 = 6 edges + assert_eq!(target.num_vertices(), 5); + assert_eq!(target.num_edges(), 6); + + let solver = BruteForce::new(); + let best_target = solver.find_all_best(target); + let best_source: HashSet> = solver.find_all_best(&source).into_iter().collect(); + + // Extract solutions and verify they are valid source solutions + let extracted: HashSet> = best_target + .iter() + .map(|t| reduction.extract_solution(t)) + .collect(); + assert!(extracted.is_subset(&best_source)); +} + +#[test] +fn test_maximumindependentset_to_maximumclique_weighted() { + // Triangle with weights + let source = MaximumIndependentSet::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![10, 20, 30], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + // Complement of K3 has 0 edges (empty graph) + assert_eq!(target.num_vertices(), 3); + assert_eq!(target.num_edges(), 0); + assert_eq!(target.weights().to_vec(), vec![10, 20, 30]); + + // In empty graph, max clique is a single vertex. Best is vertex 2 (weight 30). + let solver = BruteForce::new(); + let best = solver.find_all_best(target); + for sol in &best { + let extracted = reduction.extract_solution(sol); + let metric = source.evaluate(&extracted); + assert!(metric.is_valid()); + } +} + +#[test] +fn test_maximumindependentset_to_maximumclique_empty_graph() { + // Empty graph (no edges) - complement is complete graph + let source = MaximumIndependentSet::new(SimpleGraph::new(4, vec![]), vec![1i32; 4]); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + // Complement of empty graph is K4 with 6 edges + assert_eq!(target.num_vertices(), 4); + assert_eq!(target.num_edges(), 6); + + // All 4 vertices form a clique in complement = all 4 are independent set in source + let solver = BruteForce::new(); + let best_target = solver.find_all_best(target); + assert!(best_target.iter().all(|s| s.iter().sum::() == 4)); +} + +#[test] +fn test_maximumindependentset_to_maximumclique_complete_graph() { + // Complete graph K4 - complement is empty graph + let source = MaximumIndependentSet::new( + SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]), + vec![1i32; 4], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.num_edges(), 0); + + // Max clique in empty graph is single vertex, max IS in K4 is also single vertex + let solver = BruteForce::new(); + let best = solver.find_all_best(target); + assert!(best.iter().all(|s| s.iter().sum::() == 1)); +} diff --git a/tests/suites/examples.rs b/tests/suites/examples.rs index 3c9ad8033..1f45bf44b 100644 --- a/tests/suites/examples.rs +++ b/tests/suites/examples.rs @@ -24,6 +24,7 @@ example_test!(reduction_ksatisfiability_to_satisfiability); example_test!(reduction_maxcut_to_spinglass); example_test!(reduction_maximumclique_to_ilp); example_test!(reduction_maximumindependentset_to_ilp); +example_test!(reduction_maximumindependentset_to_maximumclique); example_test!(reduction_maximumindependentset_to_maximumsetpacking); example_test!(reduction_maximumindependentset_to_minimumvertexcover); example_test!(reduction_maximumindependentset_to_qubo); @@ -93,6 +94,10 @@ example_fn!( test_maximumindependentset_to_ilp, reduction_maximumindependentset_to_ilp ); +example_fn!( + test_maximumindependentset_to_maximumclique, + reduction_maximumindependentset_to_maximumclique +); example_fn!( test_maximumindependentset_to_maximumsetpacking, reduction_maximumindependentset_to_maximumsetpacking From 5153bef3b8800f7da78954d8ee1f9da0eee71442 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 14:43:36 +0000 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20update=20MIS=E2=86=92ILP=20path=20te?= =?UTF-8?q?st=20to=20expect=20MaxClique=20intermediate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding the MIS→MaxClique reduction created a shorter path through the reduction graph, changing MIS→ILP from [MIS, MaxSetPacking, ILP] to [MIS, MaxClique, ILP]. Co-Authored-By: Claude Opus 4.6 --- src/unit_tests/rules/maximumindependentset_ilp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unit_tests/rules/maximumindependentset_ilp.rs b/src/unit_tests/rules/maximumindependentset_ilp.rs index cc6dd7a58..2c2166119 100644 --- a/src/unit_tests/rules/maximumindependentset_ilp.rs +++ b/src/unit_tests/rules/maximumindependentset_ilp.rs @@ -43,7 +43,7 @@ fn test_maximumindependentset_to_ilp_via_path_structure() { ); assert_eq!( path.type_names(), - vec!["MaximumIndependentSet", "MaximumSetPacking", "ILP"] + vec!["MaximumIndependentSet", "MaximumClique", "ILP"] ); assert_eq!(ilp.num_vars, 3); assert_eq!(ilp.constraints.len(), 3); From 3a22df6a65ae71fc3afd08b501b28a3d3340d083 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 14:44:20 +0000 Subject: [PATCH 3/6] =?UTF-8?q?chore:=20regenerate=20reduction=5Fgraph.jso?= =?UTF-8?q?n=20with=20MIS=E2=86=92MaxClique=20edge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/reduction_graph.json | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index e189c002a..e0c641abb 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -1,14 +1,3 @@ -{ - "nodes": [ - { - "name": "BMF", - "variant": {}, - "category": "algebraic", - "doc_path": "models/algebraic/struct.BMF.html", - "complexity": "2^(rows * rank + r -Exported to: docs/src/reductions/reduction_graph.json - -JSON content: { "nodes": [ { @@ -731,6 +720,21 @@ JSON content: ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, + { + "source": 25, + "target": 21, + "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": 25, "target": 32, @@ -1155,4 +1159,4 @@ JSON content: "doc_path": "rules/travelingsalesman_ilp/index.html" } ] -} +} \ No newline at end of file From 9d826bc2c548fb5dd917eeef06cee53b17a205c2 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 15:37:19 +0000 Subject: [PATCH 4/6] fix: merge main, regenerate reduction graph, fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Merge latest main to pick up new models/rules - Regenerate reduction_graph.json with MIS→MaxClique edge - Fix rustfmt formatting in detect_isolated_problems.rs Co-Authored-By: Claude Opus 4.6 --- .../maximumindependentset_to_maximumclique.json | 4 ++-- docs/src/reductions/reduction_graph.json | 15 +++++++++++++++ examples/detect_isolated_problems.rs | 3 +-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/paper/examples/maximumindependentset_to_maximumclique.json b/docs/paper/examples/maximumindependentset_to_maximumclique.json index 352ddeb68..abcd99576 100644 --- a/docs/paper/examples/maximumindependentset_to_maximumclique.json +++ b/docs/paper/examples/maximumindependentset_to_maximumclique.json @@ -2,8 +2,8 @@ "source": { "problem": "MaximumIndependentSet", "variant": { - "graph": "SimpleGraph", - "weight": "i32" + "weight": "i32", + "graph": "SimpleGraph" }, "instance": { "edges": [ diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index ce418d783..5dc6c8bcf 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -747,6 +747,21 @@ ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, + { + "source": 26, + "target": 22, + "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": 26, "target": 33, diff --git a/examples/detect_isolated_problems.rs b/examples/detect_isolated_problems.rs index 209b61b17..5e6c5c1e4 100644 --- a/examples/detect_isolated_problems.rs +++ b/examples/detect_isolated_problems.rs @@ -107,8 +107,7 @@ fn main() { let label = if v.is_empty() { name.to_string() } else { - let parts: Vec = - v.iter().map(|(k, val)| format!("{k}: {val}")).collect(); + let parts: Vec = v.iter().map(|(k, val)| format!("{k}: {val}")).collect(); format!("{name} {{{}}}", parts.join(", ")) }; if let Some(c) = graph.variant_complexity(name, v) { From 9f87871d83777700e522c8cb2e232390305b7ade Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 15:50:37 +0000 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20make=20MIS=E2=86=92ILP=20path=20test?= =?UTF-8?q?=20accept=20either=20MaxClique=20or=20MaxSetPacking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both are valid 2-step paths with the same cost. The tie-breaking depends on hash map iteration order which differs across platforms. Co-Authored-By: Claude Opus 4.6 --- src/unit_tests/rules/maximumindependentset_ilp.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/unit_tests/rules/maximumindependentset_ilp.rs b/src/unit_tests/rules/maximumindependentset_ilp.rs index 2c2166119..122ab6abe 100644 --- a/src/unit_tests/rules/maximumindependentset_ilp.rs +++ b/src/unit_tests/rules/maximumindependentset_ilp.rs @@ -41,9 +41,12 @@ fn test_maximumindependentset_to_ilp_via_path_structure() { path.len() > 1, "Removed rule should be exercised through a multi-step path" ); - assert_eq!( - path.type_names(), - vec!["MaximumIndependentSet", "MaximumClique", "ILP"] + let names = path.type_names(); + assert!( + names == vec!["MaximumIndependentSet", "MaximumClique", "ILP"] + || names == vec!["MaximumIndependentSet", "MaximumSetPacking", "ILP"], + "Expected 2-step path through MaxClique or MaxSetPacking, got {:?}", + names ); assert_eq!(ilp.num_vars, 3); assert_eq!(ilp.constraints.len(), 3); From e40e08125407bc40ceb28e018a482324f9b72e79 Mon Sep 17 00:00:00 2001 From: zazabap Date: Thu, 12 Mar 2026 15:51:52 +0000 Subject: [PATCH 6/6] fix: regenerate example JSON with consistent key ordering Co-Authored-By: Claude Opus 4.6 --- .../examples/maximumindependentset_to_maximumclique.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/paper/examples/maximumindependentset_to_maximumclique.json b/docs/paper/examples/maximumindependentset_to_maximumclique.json index abcd99576..21a8853eb 100644 --- a/docs/paper/examples/maximumindependentset_to_maximumclique.json +++ b/docs/paper/examples/maximumindependentset_to_maximumclique.json @@ -2,8 +2,8 @@ "source": { "problem": "MaximumIndependentSet", "variant": { - "weight": "i32", - "graph": "SimpleGraph" + "graph": "SimpleGraph", + "weight": "i32" }, "instance": { "edges": [ @@ -31,8 +31,8 @@ "target": { "problem": "MaximumClique", "variant": { - "graph": "SimpleGraph", - "weight": "i32" + "weight": "i32", + "graph": "SimpleGraph" }, "instance": { "edges": [