Skip to content

Commit b3b18f1

Browse files
GiggleLiuclaude
andauthored
Add MaximumClique<SimpleGraph, One> variant with weight cast and reductions (#1055)
* Add MaximumClique<SimpleGraph, One> 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) <noreply@anthropic.com> * Drop dominated MaxClique cast; add <One> closed-loop tests The `MaximumClique<SG,One> → MaximumClique<SG,i32>` direct cast is dominated by the existing `MC<SG,One> → MIS<SG,One> → MIS<SG,i32> → MC<SG,i32>` 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 `<One> → <i32>` path is still reachable via MIS, so `pred path MaximumIndependentSet MaximumClique` stays on `One` without the `i32` detour. Also: - add `<One>` closed-loop tests for both MC↔MIS reduction directions and a `MaximumClique<SG,One>` 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) <noreply@anthropic.com> * Add canonical rule examples for <One> MC↔MIS reductions The test `canonical_rule_examples_cover_exactly_authored_direct_reductions` requires every registered `#[reduction]` edge to have a matching canonical `RuleExampleSpec`. The new `<SimpleGraph, One>` 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) <noreply@anthropic.com> * Pin MC↔MIS paper examples to i32 weight variant `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) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9ff62f0 commit b3b18f1

9 files changed

Lines changed: 233 additions & 88 deletions

docs/paper/reductions.typ

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11098,10 +11098,17 @@ Each reduction is presented as a *Rule* (with linked problem names and overhead
1109811098
]
1109911099

1110011100

11101-
#let mis_clique = load-example("MaximumIndependentSet", "MaximumClique")
11101+
#let mis_clique = load-example(
11102+
"MaximumIndependentSet",
11103+
"MaximumClique",
11104+
source-variant: (graph: "SimpleGraph", weight: "i32"),
11105+
target-variant: (graph: "SimpleGraph", weight: "i32"),
11106+
)
1110211107
#let mis_clique_sol = mis_clique.solutions.at(0)
1110311108
#reduction-rule("MaximumIndependentSet", "MaximumClique",
1110411109
example: true,
11110+
example-source-variant: (graph: "SimpleGraph", weight: "i32"),
11111+
example-target-variant: (graph: "SimpleGraph", weight: "i32"),
1110511112
example-caption: [Path graph $P_5$: IS $arrow.r$ Clique via complement],
1110611113
extra: [
1110711114
#pred-commands(
@@ -12689,10 +12696,17 @@ The following reductions to Integer Linear Programming are straightforward formu
1268912696
_Solution extraction._ Identity: return the ILP variable vector $bold(c)$ as the Integer Knapsack multiplicities.
1269012697
]
1269112698

12692-
#let clique_mis = load-example("MaximumClique", "MaximumIndependentSet")
12699+
#let clique_mis = load-example(
12700+
"MaximumClique",
12701+
"MaximumIndependentSet",
12702+
source-variant: (graph: "SimpleGraph", weight: "i32"),
12703+
target-variant: (graph: "SimpleGraph", weight: "i32"),
12704+
)
1269312705
#let clique_mis_sol = clique_mis.solutions.at(0)
1269412706
#reduction-rule("MaximumClique", "MaximumIndependentSet",
1269512707
example: true,
12708+
example-source-variant: (graph: "SimpleGraph", weight: "i32"),
12709+
example-target-variant: (graph: "SimpleGraph", weight: "i32"),
1269612710
example-caption: [Path graph $P_4$: clique in $G$ maps to independent set in complement $overline(G)$.],
1269712711
extra: [
1269812712
#pred-commands(

src/models/graph/maximum_clique.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
77
use crate::topology::{Graph, SimpleGraph};
88
use crate::traits::Problem;
9-
use crate::types::{Max, WeightElement};
9+
use crate::types::{Max, One, WeightElement};
1010
use num_traits::Zero;
1111
use serde::{Deserialize, Serialize};
1212

@@ -17,7 +17,7 @@ inventory::submit! {
1717
aliases: &[],
1818
dimensions: &[
1919
VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20-
VariantDimension::new("weight", "i32", &["i32"]),
20+
VariantDimension::new("weight", "One", &["One", "i32"]),
2121
],
2222
module_path: module_path!(),
2323
description: "Find maximum weight clique in a graph",
@@ -165,7 +165,8 @@ fn is_clique_config<G: Graph>(graph: &G, config: &[usize]) -> bool {
165165
}
166166

167167
crate::declare_variants! {
168-
default MaximumClique<SimpleGraph, i32> => "1.1996^num_vertices",
168+
MaximumClique<SimpleGraph, i32> => "1.1996^num_vertices",
169+
default MaximumClique<SimpleGraph, One> => "1.1996^num_vertices",
169170
}
170171

171172
#[cfg(feature = "example-db")]

src/rules/graph_helpers.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Shared helpers for graph-based reductions.
22
3-
use crate::topology::Graph;
3+
use crate::topology::{Graph, SimpleGraph};
44

55
/// Extract a Hamiltonian cycle vertex ordering from edge-selection configs on complete graphs.
66
///
@@ -57,3 +57,17 @@ pub(crate) fn edges_to_cycle_order<G: Graph>(graph: &G, target_solution: &[usize
5757

5858
order
5959
}
60+
61+
/// Build the complement graph edges: edges between all non-adjacent vertex pairs.
62+
pub(crate) fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> {
63+
let n = graph.num_vertices();
64+
let mut edges = Vec::new();
65+
for u in 0..n {
66+
for v in (u + 1)..n {
67+
if !graph.has_edge(u, v) {
68+
edges.push((u, v));
69+
}
70+
}
71+
}
72+
edges
73+
}

src/rules/kcoloring_partitionintocliques.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use crate::models::graph::{KColoring, PartitionIntoCliques};
77
use crate::reduction;
8+
use crate::rules::graph_helpers::complement_edges;
89
use crate::rules::traits::{ReduceTo, ReductionResult};
910
use crate::topology::{Graph, SimpleGraph};
1011
use crate::variant::KN;
@@ -29,19 +30,6 @@ impl ReductionResult for ReductionKColoringToPartitionIntoCliques {
2930
}
3031
}
3132

32-
fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> {
33-
let n = graph.num_vertices();
34-
let mut edges = Vec::new();
35-
for u in 0..n {
36-
for v in (u + 1)..n {
37-
if !graph.has_edge(u, v) {
38-
edges.push((u, v));
39-
}
40-
}
41-
}
42-
edges
43-
}
44-
4533
#[reduction(
4634
overhead = {
4735
num_vertices = "num_vertices",

src/rules/maximumclique_maximumindependentset.rs

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::models::graph::{MaximumClique, MaximumIndependentSet};
77
use crate::reduction;
88
use crate::rules::traits::{ReduceTo, ReductionResult};
99
use crate::topology::{Graph, SimpleGraph};
10-
use crate::types::WeightElement;
10+
use crate::types::{One, WeightElement};
1111

1212
/// Result of reducing MaximumClique to MaximumIndependentSet.
1313
#[derive(Debug, Clone)]
@@ -33,18 +33,15 @@ where
3333
}
3434
}
3535

36-
/// Build the complement graph: edges between all non-adjacent vertex pairs.
37-
fn complement_edges(graph: &SimpleGraph) -> Vec<(usize, usize)> {
38-
let n = graph.num_vertices();
39-
let mut edges = Vec::new();
40-
for u in 0..n {
41-
for v in (u + 1)..n {
42-
if !graph.has_edge(u, v) {
43-
edges.push((u, v));
44-
}
45-
}
46-
}
47-
edges
36+
fn reduce_clique_to_is<W: WeightElement>(
37+
src: &MaximumClique<SimpleGraph, W>,
38+
) -> ReductionCliqueToIS<W> {
39+
let comp_edges = super::graph_helpers::complement_edges(src.graph());
40+
let target = MaximumIndependentSet::new(
41+
SimpleGraph::new(src.graph().num_vertices(), comp_edges),
42+
src.weights().to_vec(),
43+
);
44+
ReductionCliqueToIS { target }
4845
}
4946

5047
#[reduction(
@@ -57,38 +54,68 @@ impl ReduceTo<MaximumIndependentSet<SimpleGraph, i32>> for MaximumClique<SimpleG
5754
type Result = ReductionCliqueToIS<i32>;
5855

5956
fn reduce_to(&self) -> Self::Result {
60-
let comp_edges = complement_edges(self.graph());
61-
let target = MaximumIndependentSet::new(
62-
SimpleGraph::new(self.graph().num_vertices(), comp_edges),
63-
self.weights().to_vec(),
64-
);
65-
ReductionCliqueToIS { target }
57+
reduce_clique_to_is(self)
58+
}
59+
}
60+
61+
#[reduction(
62+
overhead = {
63+
num_vertices = "num_vertices",
64+
num_edges = "num_vertices * (num_vertices - 1) / 2 - num_edges",
65+
}
66+
)]
67+
impl ReduceTo<MaximumIndependentSet<SimpleGraph, One>> for MaximumClique<SimpleGraph, One> {
68+
type Result = ReductionCliqueToIS<One>;
69+
70+
fn reduce_to(&self) -> Self::Result {
71+
reduce_clique_to_is(self)
6672
}
6773
}
6874

6975
#[cfg(feature = "example-db")]
7076
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
7177
use crate::export::SolutionPair;
7278

73-
vec![crate::example_db::specs::RuleExampleSpec {
74-
id: "maximumclique_to_maximumindependentset",
75-
build: || {
76-
let source = MaximumClique::new(
77-
SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]),
78-
vec![1i32; 4],
79-
);
80-
crate::example_db::specs::rule_example_with_witness::<
81-
_,
82-
MaximumIndependentSet<SimpleGraph, i32>,
83-
>(
84-
source,
85-
SolutionPair {
86-
source_config: vec![0, 1, 1, 0],
87-
target_config: vec![0, 1, 1, 0],
88-
},
89-
)
79+
vec![
80+
crate::example_db::specs::RuleExampleSpec {
81+
id: "maximumclique_to_maximumindependentset",
82+
build: || {
83+
let source = MaximumClique::new(
84+
SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]),
85+
vec![1i32; 4],
86+
);
87+
crate::example_db::specs::rule_example_with_witness::<
88+
_,
89+
MaximumIndependentSet<SimpleGraph, i32>,
90+
>(
91+
source,
92+
SolutionPair {
93+
source_config: vec![0, 1, 1, 0],
94+
target_config: vec![0, 1, 1, 0],
95+
},
96+
)
97+
},
98+
},
99+
crate::example_db::specs::RuleExampleSpec {
100+
id: "maximumclique_to_maximumindependentset_one",
101+
build: || {
102+
let source = MaximumClique::new(
103+
SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]),
104+
vec![One; 4],
105+
);
106+
crate::example_db::specs::rule_example_with_witness::<
107+
_,
108+
MaximumIndependentSet<SimpleGraph, One>,
109+
>(
110+
source,
111+
SolutionPair {
112+
source_config: vec![0, 1, 1, 0],
113+
target_config: vec![0, 1, 1, 0],
114+
},
115+
)
116+
},
90117
},
91-
}]
118+
]
92119
}
93120

94121
#[cfg(test)]

src/rules/maximumindependentset_maximumclique.rs

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::models::graph::{MaximumClique, MaximumIndependentSet};
77
use crate::reduction;
88
use crate::rules::traits::{ReduceTo, ReductionResult};
99
use crate::topology::{Graph, SimpleGraph};
10-
use crate::types::WeightElement;
10+
use crate::types::{One, WeightElement};
1111

1212
/// Result of reducing MaximumIndependentSet to MaximumClique.
1313
#[derive(Debug, Clone)]
@@ -33,6 +33,17 @@ where
3333
}
3434
}
3535

36+
fn reduce_is_to_clique<W: WeightElement>(
37+
src: &MaximumIndependentSet<SimpleGraph, W>,
38+
) -> ReductionISToClique<W> {
39+
let comp_edges = super::graph_helpers::complement_edges(src.graph());
40+
let target = MaximumClique::new(
41+
SimpleGraph::new(src.graph().num_vertices(), comp_edges),
42+
src.weights().to_vec(),
43+
);
44+
ReductionISToClique { target }
45+
}
46+
3647
#[reduction(
3748
overhead = {
3849
num_vertices = "num_vertices",
@@ -43,44 +54,68 @@ impl ReduceTo<MaximumClique<SimpleGraph, i32>> for MaximumIndependentSet<SimpleG
4354
type Result = ReductionISToClique<i32>;
4455

4556
fn reduce_to(&self) -> Self::Result {
46-
let n = self.graph().num_vertices();
47-
// Build complement graph edges
48-
let mut complement_edges = Vec::new();
49-
for u in 0..n {
50-
for v in (u + 1)..n {
51-
if !self.graph().has_edge(u, v) {
52-
complement_edges.push((u, v));
53-
}
54-
}
55-
}
56-
let target = MaximumClique::new(
57-
SimpleGraph::new(n, complement_edges),
58-
self.weights().to_vec(),
59-
);
60-
ReductionISToClique { target }
57+
reduce_is_to_clique(self)
58+
}
59+
}
60+
61+
#[reduction(
62+
overhead = {
63+
num_vertices = "num_vertices",
64+
num_edges = "num_vertices * (num_vertices - 1) / 2 - num_edges",
65+
}
66+
)]
67+
impl ReduceTo<MaximumClique<SimpleGraph, One>> for MaximumIndependentSet<SimpleGraph, One> {
68+
type Result = ReductionISToClique<One>;
69+
70+
fn reduce_to(&self) -> Self::Result {
71+
reduce_is_to_clique(self)
6172
}
6273
}
6374

6475
#[cfg(feature = "example-db")]
6576
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
6677
use crate::export::SolutionPair;
6778

68-
vec![crate::example_db::specs::RuleExampleSpec {
69-
id: "maximumindependentset_to_maximumclique",
70-
build: || {
71-
let source = MaximumIndependentSet::new(
72-
SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]),
73-
vec![1i32; 5],
74-
);
75-
crate::example_db::specs::rule_example_with_witness::<_, MaximumClique<SimpleGraph, i32>>(
76-
source,
77-
SolutionPair {
78-
source_config: vec![1, 0, 1, 0, 1],
79-
target_config: vec![1, 0, 1, 0, 1],
80-
},
81-
)
79+
vec![
80+
crate::example_db::specs::RuleExampleSpec {
81+
id: "maximumindependentset_to_maximumclique",
82+
build: || {
83+
let source = MaximumIndependentSet::new(
84+
SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]),
85+
vec![1i32; 5],
86+
);
87+
crate::example_db::specs::rule_example_with_witness::<
88+
_,
89+
MaximumClique<SimpleGraph, i32>,
90+
>(
91+
source,
92+
SolutionPair {
93+
source_config: vec![1, 0, 1, 0, 1],
94+
target_config: vec![1, 0, 1, 0, 1],
95+
},
96+
)
97+
},
98+
},
99+
crate::example_db::specs::RuleExampleSpec {
100+
id: "maximumindependentset_to_maximumclique_one",
101+
build: || {
102+
let source = MaximumIndependentSet::new(
103+
SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]),
104+
vec![One; 5],
105+
);
106+
crate::example_db::specs::rule_example_with_witness::<
107+
_,
108+
MaximumClique<SimpleGraph, One>,
109+
>(
110+
source,
111+
SolutionPair {
112+
source_config: vec![1, 0, 1, 0, 1],
113+
target_config: vec![1, 0, 1, 0, 1],
114+
},
115+
)
116+
},
82117
},
83-
}]
118+
]
84119
}
85120

86121
#[cfg(test)]

0 commit comments

Comments
 (0)