From 1e17e9dfbdf6a03c9352155acd2fb05ead8cb3c4 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 18 Apr 2026 07:57:07 +0800 Subject: [PATCH 1/4] Add MaximumClique variant with weight cast and reductions Register the One (unit weight) variant for MaximumClique so reduction paths from MIS/SimpleGraph/One no longer require unnecessary weight promotion to i32 before reaching MaxClique. Consolidate complement_edges into graph_helpers to eliminate 3 duplicate copies (DRY). Co-Authored-By: Claude Opus 4.6 (1M context) --- src/models/graph/maximum_clique.rs | 7 +-- src/rules/graph_helpers.rs | 16 ++++++- src/rules/kcoloring_partitionintocliques.rs | 13 +----- src/rules/maximumclique_casts.rs | 18 ++++++++ .../maximumclique_maximumindependentset.rs | 44 +++++++++++-------- .../maximumindependentset_maximumclique.rs | 43 +++++++++++------- src/rules/mod.rs | 1 + 7 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 src/rules/maximumclique_casts.rs diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index 6dd4cb284..849bef38a 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -6,7 +6,7 @@ use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension}; use crate::topology::{Graph, SimpleGraph}; use crate::traits::Problem; -use crate::types::{Max, WeightElement}; +use crate::types::{Max, One, WeightElement}; use num_traits::Zero; use serde::{Deserialize, Serialize}; @@ -17,7 +17,7 @@ inventory::submit! { aliases: &[], dimensions: &[ VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]), - VariantDimension::new("weight", "i32", &["i32"]), + VariantDimension::new("weight", "One", &["One", "i32"]), ], module_path: module_path!(), description: "Find maximum weight clique in a graph", @@ -165,7 +165,8 @@ fn is_clique_config(graph: &G, config: &[usize]) -> bool { } crate::declare_variants! { - default MaximumClique => "1.1996^num_vertices", + MaximumClique => "1.1996^num_vertices", + default MaximumClique => "1.1996^num_vertices", } #[cfg(feature = "example-db")] diff --git a/src/rules/graph_helpers.rs b/src/rules/graph_helpers.rs index 58c54a34e..bdc02ae88 100644 --- a/src/rules/graph_helpers.rs +++ b/src/rules/graph_helpers.rs @@ -1,6 +1,6 @@ //! Shared helpers for graph-based reductions. -use crate::topology::Graph; +use crate::topology::{Graph, SimpleGraph}; /// Extract a Hamiltonian cycle vertex ordering from edge-selection configs on complete graphs. /// @@ -57,3 +57,17 @@ pub(crate) fn edges_to_cycle_order(graph: &G, target_solution: &[usize order } + +/// Build the complement graph edges: edges between all non-adjacent vertex pairs. +pub(crate) fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> { + let n = graph.num_vertices(); + let mut edges = Vec::new(); + for u in 0..n { + for v in (u + 1)..n { + if !graph.has_edge(u, v) { + edges.push((u, v)); + } + } + } + edges +} diff --git a/src/rules/kcoloring_partitionintocliques.rs b/src/rules/kcoloring_partitionintocliques.rs index 156f8c781..34f8b11e7 100644 --- a/src/rules/kcoloring_partitionintocliques.rs +++ b/src/rules/kcoloring_partitionintocliques.rs @@ -29,18 +29,7 @@ impl ReductionResult for ReductionKColoringToPartitionIntoCliques { } } -fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> { - let n = graph.num_vertices(); - let mut edges = Vec::new(); - for u in 0..n { - for v in (u + 1)..n { - if !graph.has_edge(u, v) { - edges.push((u, v)); - } - } - } - edges -} +use super::graph_helpers::complement_edges; #[reduction( overhead = { diff --git a/src/rules/maximumclique_casts.rs b/src/rules/maximumclique_casts.rs new file mode 100644 index 000000000..eeac7438e --- /dev/null +++ b/src/rules/maximumclique_casts.rs @@ -0,0 +1,18 @@ +//! Variant cast reductions for MaximumClique. +//! +//! Weight-hierarchy cast converting MaximumClique between weight subtypes. + +use crate::impl_variant_reduction; +use crate::models::graph::MaximumClique; +use crate::topology::SimpleGraph; +use crate::types::One; +use crate::variant::CastToParent; + +// Weight-hierarchy cast (One → i32) +impl_variant_reduction!( + MaximumClique, + => , + fields: [num_vertices, num_edges], + |src| MaximumClique::new( + src.graph().clone(), src.weights().iter().map(|w| w.cast_to_parent()).collect()) +); diff --git a/src/rules/maximumclique_maximumindependentset.rs b/src/rules/maximumclique_maximumindependentset.rs index 38c00bcd3..d7c8873a0 100644 --- a/src/rules/maximumclique_maximumindependentset.rs +++ b/src/rules/maximumclique_maximumindependentset.rs @@ -7,7 +7,7 @@ use crate::models::graph::{MaximumClique, MaximumIndependentSet}; use crate::reduction; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::{Graph, SimpleGraph}; -use crate::types::WeightElement; +use crate::types::{One, WeightElement}; /// Result of reducing MaximumClique to MaximumIndependentSet. #[derive(Debug, Clone)] @@ -33,18 +33,15 @@ where } } -/// Build the complement graph: edges between all non-adjacent vertex pairs. -fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> { - let n = graph.num_vertices(); - let mut edges = Vec::new(); - for u in 0..n { - for v in (u + 1)..n { - if !graph.has_edge(u, v) { - edges.push((u, v)); - } - } - } - edges +fn reduce_clique_to_is( + src: &MaximumClique, +) -> ReductionCliqueToIS { + let comp_edges = super::graph_helpers::complement_edges(src.graph()); + let target = MaximumIndependentSet::new( + SimpleGraph::new(src.graph().num_vertices(), comp_edges), + src.weights().to_vec(), + ); + ReductionCliqueToIS { target } } #[reduction( @@ -57,12 +54,21 @@ impl ReduceTo> for MaximumClique; fn reduce_to(&self) -> Self::Result { - let comp_edges = complement_edges(self.graph()); - let target = MaximumIndependentSet::new( - SimpleGraph::new(self.graph().num_vertices(), comp_edges), - self.weights().to_vec(), - ); - ReductionCliqueToIS { target } + reduce_clique_to_is(self) + } +} + +#[reduction( + overhead = { + num_vertices = "num_vertices", + num_edges = "num_vertices * (num_vertices - 1) / 2 - num_edges", + } +)] +impl ReduceTo> for MaximumClique { + type Result = ReductionCliqueToIS; + + fn reduce_to(&self) -> Self::Result { + reduce_clique_to_is(self) } } diff --git a/src/rules/maximumindependentset_maximumclique.rs b/src/rules/maximumindependentset_maximumclique.rs index 5701b3c4d..80af9bc9f 100644 --- a/src/rules/maximumindependentset_maximumclique.rs +++ b/src/rules/maximumindependentset_maximumclique.rs @@ -7,7 +7,7 @@ use crate::models::graph::{MaximumClique, MaximumIndependentSet}; use crate::reduction; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::{Graph, SimpleGraph}; -use crate::types::WeightElement; +use crate::types::{One, WeightElement}; /// Result of reducing MaximumIndependentSet to MaximumClique. #[derive(Debug, Clone)] @@ -33,6 +33,17 @@ where } } +fn reduce_is_to_clique( + src: &MaximumIndependentSet, +) -> ReductionISToClique { + let comp_edges = super::graph_helpers::complement_edges(src.graph()); + let target = MaximumClique::new( + SimpleGraph::new(src.graph().num_vertices(), comp_edges), + src.weights().to_vec(), + ); + ReductionISToClique { target } +} + #[reduction( overhead = { num_vertices = "num_vertices", @@ -43,21 +54,21 @@ impl ReduceTo> for MaximumIndependentSet; 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 } + reduce_is_to_clique(self) + } +} + +#[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 { + reduce_is_to_clique(self) } } diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 8c5185851..549e88b40 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -71,6 +71,7 @@ pub(crate) mod ksatisfiability_timetabledesign; pub(crate) mod longestcommonsubsequence_maximumindependentset; pub(crate) mod maxcut_minimumcutintoboundedsets; pub(crate) mod maximum2satisfiability_maxcut; +mod maximumclique_casts; pub(crate) mod maximumclique_maximumindependentset; mod maximumindependentset_casts; mod maximumindependentset_gridgraph; From 1abc32be96947a7b9a73581268a50c473b6d7d77 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 21 Apr 2026 21:10:37 +0800 Subject: [PATCH 2/4] Drop dominated MaxClique cast; add closed-loop tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `MaximumClique → MaximumClique` direct cast is dominated by the existing `MC → MIS → MIS → MC` path, which the redundancy-sanity test flags in `rules::analysis::tests::test_find_dominated_rules_returns_known_set`. Remove the cast entirely (and its now-empty `maximumclique_casts.rs`); the cast-less `` path is still reachable via MIS, so `pred path MaximumIndependentSet MaximumClique` stays on `One` without the `i32` detour. Also: - add `` closed-loop tests for both MC↔MIS reduction directions and a `MaximumClique` model-level evaluate/solve test, - drop the redundant `Clone + Default` bounds on `reduce_clique_to_is`/`reduce_is_to_clique` (already implied by `WeightElement`), - move the stray `use super::graph_helpers::complement_edges;` in `kcoloring_partitionintocliques.rs` up with the other imports. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/rules/kcoloring_partitionintocliques.rs | 3 +-- src/rules/maximumclique_casts.rs | 18 ------------- .../maximumclique_maximumindependentset.rs | 2 +- .../maximumindependentset_maximumclique.rs | 2 +- src/rules/mod.rs | 1 - src/unit_tests/models/graph/maximum_clique.rs | 25 ++++++++++++++++++- .../maximumclique_maximumindependentset.rs | 22 ++++++++++++++++ .../maximumindependentset_maximumclique.rs | 21 ++++++++++++++++ 8 files changed, 70 insertions(+), 24 deletions(-) delete mode 100644 src/rules/maximumclique_casts.rs diff --git a/src/rules/kcoloring_partitionintocliques.rs b/src/rules/kcoloring_partitionintocliques.rs index 34f8b11e7..081a0d82c 100644 --- a/src/rules/kcoloring_partitionintocliques.rs +++ b/src/rules/kcoloring_partitionintocliques.rs @@ -5,6 +5,7 @@ use crate::models::graph::{KColoring, PartitionIntoCliques}; use crate::reduction; +use crate::rules::graph_helpers::complement_edges; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::{Graph, SimpleGraph}; use crate::variant::KN; @@ -29,8 +30,6 @@ impl ReductionResult for ReductionKColoringToPartitionIntoCliques { } } -use super::graph_helpers::complement_edges; - #[reduction( overhead = { num_vertices = "num_vertices", diff --git a/src/rules/maximumclique_casts.rs b/src/rules/maximumclique_casts.rs deleted file mode 100644 index eeac7438e..000000000 --- a/src/rules/maximumclique_casts.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Variant cast reductions for MaximumClique. -//! -//! Weight-hierarchy cast converting MaximumClique between weight subtypes. - -use crate::impl_variant_reduction; -use crate::models::graph::MaximumClique; -use crate::topology::SimpleGraph; -use crate::types::One; -use crate::variant::CastToParent; - -// Weight-hierarchy cast (One → i32) -impl_variant_reduction!( - MaximumClique, - => , - fields: [num_vertices, num_edges], - |src| MaximumClique::new( - src.graph().clone(), src.weights().iter().map(|w| w.cast_to_parent()).collect()) -); diff --git a/src/rules/maximumclique_maximumindependentset.rs b/src/rules/maximumclique_maximumindependentset.rs index d7c8873a0..efe17389d 100644 --- a/src/rules/maximumclique_maximumindependentset.rs +++ b/src/rules/maximumclique_maximumindependentset.rs @@ -33,7 +33,7 @@ where } } -fn reduce_clique_to_is( +fn reduce_clique_to_is( src: &MaximumClique, ) -> ReductionCliqueToIS { let comp_edges = super::graph_helpers::complement_edges(src.graph()); diff --git a/src/rules/maximumindependentset_maximumclique.rs b/src/rules/maximumindependentset_maximumclique.rs index 80af9bc9f..5c3f0a3b7 100644 --- a/src/rules/maximumindependentset_maximumclique.rs +++ b/src/rules/maximumindependentset_maximumclique.rs @@ -33,7 +33,7 @@ where } } -fn reduce_is_to_clique( +fn reduce_is_to_clique( src: &MaximumIndependentSet, ) -> ReductionISToClique { let comp_edges = super::graph_helpers::complement_edges(src.graph()); diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 549e88b40..8c5185851 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -71,7 +71,6 @@ pub(crate) mod ksatisfiability_timetabledesign; pub(crate) mod longestcommonsubsequence_maximumindependentset; pub(crate) mod maxcut_minimumcutintoboundedsets; pub(crate) mod maximum2satisfiability_maxcut; -mod maximumclique_casts; pub(crate) mod maximumclique_maximumindependentset; mod maximumindependentset_casts; mod maximumindependentset_gridgraph; diff --git a/src/unit_tests/models/graph/maximum_clique.rs b/src/unit_tests/models/graph/maximum_clique.rs index 625d33b10..a91da8e33 100644 --- a/src/unit_tests/models/graph/maximum_clique.rs +++ b/src/unit_tests/models/graph/maximum_clique.rs @@ -1,7 +1,7 @@ use super::*; use crate::solvers::BruteForce; use crate::topology::SimpleGraph; -use crate::types::Max; +use crate::types::{Max, One}; #[test] fn test_clique_creation() { @@ -281,6 +281,29 @@ fn test_size_getters() { assert_eq!(problem.num_edges(), 2); } +#[test] +fn test_clique_one_weights_evaluate_and_solve() { + use crate::traits::Problem; + + // Triangle with unit weights: max clique covers all 3 vertices. + let problem = MaximumClique::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![One; 3], + ); + assert!(!problem.is_weighted()); + assert_eq!(problem.evaluate(&[1, 1, 1]), Max(Some(3))); + assert_eq!(problem.evaluate(&[1, 1, 0]), Max(Some(2))); + // Invalid clique on this graph? K3 is complete, so every subset is a clique. + // Re-verify invalidity on a path graph: + let path = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![One; 3]); + assert_eq!(path.evaluate(&[1, 0, 1]), Max(None)); + + let solver = BruteForce::new(); + let solutions = solver.find_all_witnesses(&problem); + assert_eq!(solutions.len(), 1); + assert_eq!(solutions[0], vec![1, 1, 1]); +} + #[test] fn test_clique_paper_example() { use crate::traits::Problem; diff --git a/src/unit_tests/rules/maximumclique_maximumindependentset.rs b/src/unit_tests/rules/maximumclique_maximumindependentset.rs index 62520343b..39ede01a4 100644 --- a/src/unit_tests/rules/maximumclique_maximumindependentset.rs +++ b/src/unit_tests/rules/maximumclique_maximumindependentset.rs @@ -3,6 +3,7 @@ use crate::rules::test_helpers::assert_optimization_round_trip_from_optimization use crate::solvers::BruteForce; use crate::topology::Graph; use crate::traits::Problem; +use crate::types::One; #[test] fn test_maximumclique_to_maximumindependentset_closed_loop() { @@ -84,6 +85,27 @@ fn test_maximumclique_to_maximumindependentset_empty_graph() { .all(|s| s.iter().sum::() == 1)); } +#[test] +fn test_maximumclique_to_maximumindependentset_one_weights_closed_loop() { + // Same P4 as the i32 closed-loop test, but with unit weights so the + // reduction stays on the endpoint (no i32 detour). + let source = MaximumClique::new( + SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), + vec![One; 4], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.graph().num_vertices(), 4); + assert_eq!(target.graph().num_edges(), 3); + + assert_optimization_round_trip_from_optimization_target( + &source, + &reduction, + "MaximumClique->MaximumIndependentSet closed loop", + ); +} + #[test] fn test_maximumclique_to_maximumindependentset_overhead() { // Verify overhead formula: complement edges = n*(n-1)/2 - m diff --git a/src/unit_tests/rules/maximumindependentset_maximumclique.rs b/src/unit_tests/rules/maximumindependentset_maximumclique.rs index a8d485189..57e94117c 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumclique.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumclique.rs @@ -2,6 +2,7 @@ use super::*; use crate::rules::test_helpers::assert_optimization_round_trip_from_optimization_target; use crate::solvers::BruteForce; use crate::traits::Problem; +use crate::types::One; #[test] fn test_maximumindependentset_to_maximumclique_closed_loop() { @@ -66,6 +67,26 @@ fn test_maximumindependentset_to_maximumclique_empty_graph() { assert!(best_target.iter().all(|s| s.iter().sum::() == 4)); } +#[test] +fn test_maximumindependentset_to_maximumclique_one_weights_closed_loop() { + // Unit-weight closed loop: endpoint stays on One all the way. + let source = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + vec![One; 5], + ); + let reduction = ReduceTo::>::reduce_to(&source); + let target = reduction.target_problem(); + + assert_eq!(target.num_vertices(), 5); + assert_eq!(target.num_edges(), 6); + + assert_optimization_round_trip_from_optimization_target( + &source, + &reduction, + "MaximumIndependentSet->MaximumClique closed loop", + ); +} + #[test] fn test_maximumindependentset_to_maximumclique_complete_graph() { // Complete graph K4 - complement is empty graph From 29df06cecdb0a31802f92767231588c31765f9ab Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 21 Apr 2026 22:21:58 +0800 Subject: [PATCH 3/4] =?UTF-8?q?Add=20canonical=20rule=20examples=20for=20=20MC=E2=86=94MIS=20reductions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test `canonical_rule_examples_cover_exactly_authored_direct_reductions` requires every registered `#[reduction]` edge to have a matching canonical `RuleExampleSpec`. The new `` edges for `MaximumClique ↔ MaximumIndependentSet` had no corresponding example entries, so the test failed in CI (Code Coverage + Test jobs). Add parallel `_one` variants of the existing i32 examples, reusing the same P4/P5 graph shapes and solutions (the target configs are identical because the reduction is vertex-identity). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../maximumclique_maximumindependentset.rs | 57 +++++++++++++------ .../maximumindependentset_maximumclique.rs | 54 +++++++++++++----- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/src/rules/maximumclique_maximumindependentset.rs b/src/rules/maximumclique_maximumindependentset.rs index efe17389d..91bd6ebbf 100644 --- a/src/rules/maximumclique_maximumindependentset.rs +++ b/src/rules/maximumclique_maximumindependentset.rs @@ -76,25 +76,46 @@ impl ReduceTo> for MaximumClique Vec { use crate::export::SolutionPair; - vec![crate::example_db::specs::RuleExampleSpec { - id: "maximumclique_to_maximumindependentset", - build: || { - let source = MaximumClique::new( - SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), - vec![1i32; 4], - ); - crate::example_db::specs::rule_example_with_witness::< - _, - MaximumIndependentSet, - >( - source, - SolutionPair { - source_config: vec![0, 1, 1, 0], - target_config: vec![0, 1, 1, 0], - }, - ) + vec![ + crate::example_db::specs::RuleExampleSpec { + id: "maximumclique_to_maximumindependentset", + build: || { + let source = MaximumClique::new( + SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), + vec![1i32; 4], + ); + crate::example_db::specs::rule_example_with_witness::< + _, + MaximumIndependentSet, + >( + source, + SolutionPair { + source_config: vec![0, 1, 1, 0], + target_config: vec![0, 1, 1, 0], + }, + ) + }, }, - }] + crate::example_db::specs::RuleExampleSpec { + id: "maximumclique_to_maximumindependentset_one", + build: || { + let source = MaximumClique::new( + SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]), + vec![One; 4], + ); + crate::example_db::specs::rule_example_with_witness::< + _, + MaximumIndependentSet, + >( + source, + SolutionPair { + source_config: vec![0, 1, 1, 0], + target_config: vec![0, 1, 1, 0], + }, + ) + }, + }, + ] } #[cfg(test)] diff --git a/src/rules/maximumindependentset_maximumclique.rs b/src/rules/maximumindependentset_maximumclique.rs index 5c3f0a3b7..f65042b51 100644 --- a/src/rules/maximumindependentset_maximumclique.rs +++ b/src/rules/maximumindependentset_maximumclique.rs @@ -76,22 +76,46 @@ impl ReduceTo> for MaximumIndependentSet Vec { use crate::export::SolutionPair; - vec![crate::example_db::specs::RuleExampleSpec { - id: "maximumindependentset_to_maximumclique", - build: || { - let source = MaximumIndependentSet::new( - SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), - vec![1i32; 5], - ); - crate::example_db::specs::rule_example_with_witness::<_, MaximumClique>( - source, - SolutionPair { - source_config: vec![1, 0, 1, 0, 1], - target_config: vec![1, 0, 1, 0, 1], - }, - ) + vec![ + crate::example_db::specs::RuleExampleSpec { + id: "maximumindependentset_to_maximumclique", + build: || { + let source = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + vec![1i32; 5], + ); + crate::example_db::specs::rule_example_with_witness::< + _, + MaximumClique, + >( + source, + SolutionPair { + source_config: vec![1, 0, 1, 0, 1], + target_config: vec![1, 0, 1, 0, 1], + }, + ) + }, }, - }] + crate::example_db::specs::RuleExampleSpec { + id: "maximumindependentset_to_maximumclique_one", + build: || { + let source = MaximumIndependentSet::new( + SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]), + vec![One; 5], + ); + crate::example_db::specs::rule_example_with_witness::< + _, + MaximumClique, + >( + source, + SolutionPair { + source_config: vec![1, 0, 1, 0, 1], + target_config: vec![1, 0, 1, 0, 1], + }, + ) + }, + }, + ] } #[cfg(test)] From b80721b9858eaf2c58b46d95ff8168e8b594a439 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 21 Apr 2026 22:38:24 +0800 Subject: [PATCH 4/4] =?UTF-8?q?Pin=20MC=E2=86=94MIS=20paper=20examples=20t?= =?UTF-8?q?o=20i32=20weight=20variant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `canonical_rule_examples_cover_exactly_authored_direct_reductions` requires one `RuleExampleSpec` per `#[reduction]` edge, so we now have i32 and One examples for both MC↔MIS directions. But the paper's `load-example("MaximumIndependentSet", "MaximumClique")` and `load-example("MaximumClique", "MaximumIndependentSet")` calls with no variant filter become ambiguous and the paper build panics in CI's Build paper step. Pin both direct `load-example` calls and the matching `reduction-rule` `example-*-variant` kwargs to `(graph: "SimpleGraph", weight: "i32")` to preserve the pre-PR behavior (i32 was the only example before). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/paper/reductions.typ | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index f5a89d3bc..4b0e333dc 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -11098,10 +11098,17 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead ] -#let mis_clique = load-example("MaximumIndependentSet", "MaximumClique") +#let mis_clique = load-example( + "MaximumIndependentSet", + "MaximumClique", + source-variant: (graph: "SimpleGraph", weight: "i32"), + target-variant: (graph: "SimpleGraph", weight: "i32"), +) #let mis_clique_sol = mis_clique.solutions.at(0) #reduction-rule("MaximumIndependentSet", "MaximumClique", example: true, + example-source-variant: (graph: "SimpleGraph", weight: "i32"), + example-target-variant: (graph: "SimpleGraph", weight: "i32"), example-caption: [Path graph $P_5$: IS $arrow.r$ Clique via complement], extra: [ #pred-commands( @@ -12689,10 +12696,17 @@ The following reductions to Integer Linear Programming are straightforward formu _Solution extraction._ Identity: return the ILP variable vector $bold(c)$ as the Integer Knapsack multiplicities. ] -#let clique_mis = load-example("MaximumClique", "MaximumIndependentSet") +#let clique_mis = load-example( + "MaximumClique", + "MaximumIndependentSet", + source-variant: (graph: "SimpleGraph", weight: "i32"), + target-variant: (graph: "SimpleGraph", weight: "i32"), +) #let clique_mis_sol = clique_mis.solutions.at(0) #reduction-rule("MaximumClique", "MaximumIndependentSet", example: true, + example-source-variant: (graph: "SimpleGraph", weight: "i32"), + example-target-variant: (graph: "SimpleGraph", weight: "i32"), example-caption: [Path graph $P_4$: clique in $G$ maps to independent set in complement $overline(G)$.], extra: [ #pred-commands(