Skip to content

Commit 1826867

Browse files
isPANNclaude
andcommitted
Add rule: MinimumMaximalMatching -> MaximumAchromaticNumber (#846)
Classical Yannakakis-Gavril (1980) reduction establishing NP-completeness of Achromatic Number (G&J GT5): for bipartite G, ach(complement(G)) = |V| - mm(G). Adds BipartiteGraph variant of MinimumMaximalMatching as a prerequisite, the complement-graph reduction with the inverse coloring -> maximal-matching extractor, a closed-loop test plus identity tests on several bipartite instances, the canonical P4 example (acknowledged check-issue warning that the P4 canonical example has only one suboptimal maximal matching; kept for tutorial clarity), and the matching paper entry under docs/paper/reductions.typ. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 27a5c7c commit 1826867

5 files changed

Lines changed: 369 additions & 2 deletions

File tree

docs/paper/reductions.typ

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14423,6 +14423,63 @@ The following reductions to Integer Linear Programming are straightforward formu
1442314423
_Solution extraction._ $I = {v : x_v = 1}$.
1442414424
]
1442514425

14426+
#let mmm_ach = load-example("MinimumMaximalMatching", "MaximumAchromaticNumber")
14427+
#let mmm_ach_sol = mmm_ach.solutions.at(0)
14428+
#reduction-rule("MinimumMaximalMatching", "MaximumAchromaticNumber",
14429+
example: true,
14430+
example-source-variant: (graph: "BipartiteGraph"),
14431+
example-target-variant: (graph: "SimpleGraph"),
14432+
example-caption: [Path $P_4$ as a bipartite graph with $A = {v_0, v_2}$, $B = {v_1, v_3}$.],
14433+
extra: [
14434+
#{
14435+
let source-edges = mmm_ach.target.instance.graph.edges
14436+
let n-source = mmm_ach.source.instance.graph.left_size + mmm_ach.source.instance.graph.right_size
14437+
let m-source = mmm_ach.source.instance.graph.edges.len()
14438+
let m-target = mmm_ach.target.instance.graph.edges.len()
14439+
let color-of = mmm_ach_sol.target_config
14440+
let matched = mmm_ach_sol.source_config.enumerate()
14441+
.filter(((i, x)) => x == 1)
14442+
.map(((i, _)) => i)
14443+
[
14444+
#pred-commands(
14445+
"pred create --example " + problem-spec(mmm_ach.source) + " -o mmm.json",
14446+
"pred reduce mmm.json --to " + target-spec(mmm_ach) + " -o bundle.json",
14447+
"pred solve bundle.json",
14448+
"pred evaluate mmm.json --config " + mmm_ach_sol.source_config.map(str).join(","),
14449+
)
14450+
14451+
*Step 1 -- Source instance.* Path $P_4$ encoded as a bipartite graph with bipartition $A = {v_0, v_2}$ and $B = {v_1, v_3}$. In unified indices the vertex set is ${0, 1, 2, 3}$ (left vertices first), $n = #n-source$, and the $m = #m-source$ edges are #source-edges.map(e => $(#e.at(0), #e.at(1))$).join(", ").
14452+
14453+
*Step 2 -- Complement graph $H = overline(G)$.* The non-edges of $G$ in $K_4$ give the target edge set, with $|E(H)| = #m-target$ edges (#mmm_ach.target.instance.graph.edges.map(e => $(#e.at(0), #e.at(1))$).join(", ")). The decision threshold transforms as $K' = n - K$.
14454+
14455+
*Step 3 -- Source optimum.* The minimum maximal matching uses the middle edge, so $"mm"(G) = #matched.len() = 1$ (source index $#matched.at(0)$).
14456+
14457+
*Step 4 -- Target optimum.* The achromatic coloring stored in the fixture is $#color-of.map(str).join(", ")$. The size-$2$ color class corresponds to the source edge selected in Step 3, and the singletons contribute the remaining $n - 2$ classes, so the achromatic number is $psi(H) = n - "mm"(G) = #n-source - 1 = #(n-source - 1) #sym.checkmark$.
14458+
14459+
*Multiplicity:* The fixture stores one canonical witness; other valid achromatic $3$-colorings exist and would extract to other minimum maximal matchings.
14460+
]
14461+
}
14462+
],
14463+
)[
14464+
This $O(n^2)$ reduction @yannakakis1980 takes a bipartite source $G = (V, E)$ with $n = |V|$, builds the complement $H = overline(G)$ on the same vertex set, and sets the achromatic threshold to $K' = |V| - K$. For bipartite $G$, every color class of an achromatic coloring of $H$ has size at most two, and the size-two classes are exactly the edges of a maximal matching of $G$. The construction yields $|E(H)| = binom(n, 2) - |E|$ target edges.
14465+
][
14466+
_Construction._ Given a Minimum Maximal Matching instance $(G = (V, E), K)$ with $G$ bipartite, build a Maximum Achromatic Number instance $(H, K')$ where $H = (V, overline(E))$ with $overline(E) = {(u, v) : u != v, (u, v) in.not E}$ and $K' = |V| - K$.
14467+
14468+
_Correctness._ The reduction proves the identity $psi(H) = |V| - "mm"(G)$.
14469+
14470+
($arrow.r.double$) Let $M$ be a maximal matching of $G$ with $|M| <= K$. Assign one color to each edge $\{u, v\} in M$ (placing $u$ and $v$ in the same $2$-vertex class) and a distinct color to each unmatched vertex. The number of colors used is $|V| - |M| >= |V| - K = K'$.
14471+
14472+
_Proper._ Each $2$-vertex class $\{u, v\}$ is an edge of $G$, hence a clique of size $2$ in $G$, hence an independent set in $H$. Singletons are trivially independent in $H$.
14473+
14474+
_Complete._ Let $A$ and $B$ be the bipartition of $G$ and consider any two distinct classes $C_i, C_j$. Each class lies in $A$, in $B$, or is a $G$-edge with one endpoint on each side. In every case, $C_i union C_j$ contains two vertices on the same side of the bipartition. These two vertices are non-adjacent in $G$ (the bipartite property), so they are adjacent in $H$. Hence the coloring is complete and uses $|V| - |M| >= K'$ colors.
14475+
14476+
($arrow.l.double$) Let $cal(C)$ be a complete proper coloring of $H$ using $k >= K'$ colors. Because $H$ is the complement of a bipartite graph, every independent set of $H$ has size at most two, so each color class is a singleton or a pair. Let $M$ be the set of source edges $\{u, v\}$ such that $\{u, v\}$ is a $2$-vertex class. The classes are pairwise disjoint, so $M$ is a matching. The number of colors equals $k = |M| + (|V| - 2|M|) = |V| - |M|$, hence $|M| = |V| - k <= |V| - K' = K$.
14477+
14478+
_Maximality._ Suppose for contradiction that $M$ is not maximal and let $\{u, v\} in E$ have both endpoints unmatched. Then $\{u\}$ and $\{v\}$ are singleton classes in $cal(C)$. Because $\{u, v\} in E$, the pair is _not_ an edge of $H$, contradicting completeness of $cal(C)$ on the class pair $({u}, {v})$. Hence $M$ is a maximal matching with $|M| <= K$.
14479+
14480+
_Solution extraction._ Group target vertices by color. Every class of size $2$ identifies a source edge in $E$; mark those edges as selected (and leave all others unselected) to obtain a maximal matching $M$ of $G$ with $|M| = |V| - k$.
14481+
]
14482+
1442614483
#reduction-rule("MinimumMaximalMatching", "ILP")[
1442714484
Each edge is either selected or not; matching and maximality constraints are both directly linear in binary edge indicators.
1442814485
][

src/models/graph/minimum_maximal_matching.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! that is maximal (cannot be extended by adding any edge).
55
66
use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7-
use crate::topology::{Graph, SimpleGraph};
7+
use crate::topology::{BipartiteGraph, Graph, SimpleGraph};
88
use crate::traits::Problem;
99
use crate::types::Min;
1010
use serde::{Deserialize, Serialize};
@@ -15,7 +15,7 @@ inventory::submit! {
1515
display_name: "Minimum Maximal Matching",
1616
aliases: &[],
1717
dimensions: &[
18-
VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
18+
VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph", "BipartiteGraph"]),
1919
],
2020
module_path: module_path!(),
2121
description: "Find a minimum-size matching that cannot be extended",
@@ -147,6 +147,7 @@ where
147147

148148
crate::declare_variants! {
149149
default MinimumMaximalMatching<SimpleGraph> => "1.3160^num_vertices",
150+
MinimumMaximalMatching<BipartiteGraph> => "1.3160^num_vertices",
150151
}
151152

152153
#[cfg(feature = "example-db")]
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//! Reduction from MinimumMaximalMatching (on a bipartite graph) to
2+
//! MaximumAchromaticNumber.
3+
//!
4+
//! Classical reduction of Yannakakis and Gavril (1980) establishing
5+
//! NP-completeness of Achromatic Number (G&J GT5). For a bipartite graph `G`,
6+
//! the identity `ach(complement(G)) = |V| - mm(G)` holds, where `mm(G)` is the
7+
//! minimum maximal matching size of `G`. The decision-version correspondence
8+
//! used in the reduction is `(G, K) -> (complement(G), |V| - K)`.
9+
10+
use crate::models::graph::{MaximumAchromaticNumber, MinimumMaximalMatching};
11+
use crate::reduction;
12+
use crate::rules::traits::{ReduceTo, ReductionResult};
13+
use crate::topology::{BipartiteGraph, Graph, SimpleGraph};
14+
use std::collections::HashMap;
15+
16+
/// Result of reducing `MinimumMaximalMatching<BipartiteGraph>` to
17+
/// `MaximumAchromaticNumber<SimpleGraph>`.
18+
///
19+
/// Stores the target problem along with the source edge list (in unified vertex
20+
/// coordinates) so that `extract_solution` can map a target coloring back to a
21+
/// maximal matching of the source graph.
22+
#[derive(Debug, Clone)]
23+
pub struct ReductionMMMToAchromatic {
24+
target: MaximumAchromaticNumber<SimpleGraph>,
25+
/// Source edges in unified vertex coordinates, in the same order as
26+
/// `source.graph().edges()` (which determines the source `dims()`).
27+
source_edges: Vec<(usize, usize)>,
28+
}
29+
30+
impl ReductionResult for ReductionMMMToAchromatic {
31+
type Source = MinimumMaximalMatching<BipartiteGraph>;
32+
type Target = MaximumAchromaticNumber<SimpleGraph>;
33+
34+
fn target_problem(&self) -> &Self::Target {
35+
&self.target
36+
}
37+
38+
/// Extract a maximal matching of the source graph from an achromatic
39+
/// coloring of `complement(G)`.
40+
///
41+
/// Each color class of size exactly 2 corresponds to a clique-in-G of
42+
/// size 2, i.e., a single source edge. Marking those edges yields the
43+
/// maximal matching `M` with `|M| = |V| - k`, where `k` is the number of
44+
/// colors used.
45+
fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
46+
let num_source_edges = self.source_edges.len();
47+
let mut source_config = vec![0usize; num_source_edges];
48+
49+
// Group vertices by color.
50+
let mut color_to_vertices: HashMap<usize, Vec<usize>> = HashMap::new();
51+
for (vertex, &color) in target_solution.iter().enumerate() {
52+
color_to_vertices.entry(color).or_default().push(vertex);
53+
}
54+
55+
// Build an edge lookup keyed by canonical (min, max) pairs.
56+
let mut edge_index: HashMap<(usize, usize), usize> = HashMap::new();
57+
for (idx, &(u, v)) in self.source_edges.iter().enumerate() {
58+
let key = if u < v { (u, v) } else { (v, u) };
59+
edge_index.insert(key, idx);
60+
}
61+
62+
// Color classes of size 2 must be edges of G (cliques in G of size 2).
63+
for vertices in color_to_vertices.values() {
64+
if vertices.len() == 2 {
65+
let (a, b) = (vertices[0], vertices[1]);
66+
let key = if a < b { (a, b) } else { (b, a) };
67+
if let Some(&idx) = edge_index.get(&key) {
68+
source_config[idx] = 1;
69+
}
70+
}
71+
}
72+
73+
source_config
74+
}
75+
}
76+
77+
#[reduction(
78+
overhead = {
79+
num_vertices = "num_vertices",
80+
num_edges = "num_vertices * (num_vertices - 1) / 2 - num_edges",
81+
}
82+
)]
83+
impl ReduceTo<MaximumAchromaticNumber<SimpleGraph>> for MinimumMaximalMatching<BipartiteGraph> {
84+
type Result = ReductionMMMToAchromatic;
85+
86+
fn reduce_to(&self) -> Self::Result {
87+
let n = self.graph().num_vertices();
88+
let source_edges = self.graph().edges();
89+
90+
// Build adjacency lookup over unified coordinates.
91+
let source_edge_set: std::collections::HashSet<(usize, usize)> = source_edges
92+
.iter()
93+
.map(|&(u, v)| if u < v { (u, v) } else { (v, u) })
94+
.collect();
95+
96+
// Complement graph: all non-edges of G become edges of H.
97+
let mut complement_edges = Vec::new();
98+
for u in 0..n {
99+
for v in (u + 1)..n {
100+
if !source_edge_set.contains(&(u, v)) {
101+
complement_edges.push((u, v));
102+
}
103+
}
104+
}
105+
106+
let target = MaximumAchromaticNumber::new(SimpleGraph::new(n, complement_edges));
107+
108+
ReductionMMMToAchromatic {
109+
target,
110+
source_edges,
111+
}
112+
}
113+
}
114+
115+
#[cfg(feature = "example-db")]
116+
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
117+
use crate::export::SolutionPair;
118+
119+
vec![crate::example_db::specs::RuleExampleSpec {
120+
id: "minimummaximalmatching_to_maximumachromaticnumber",
121+
build: || {
122+
// Path P4 as a bipartite graph: A = {v0, v2}, B = {v1, v3}.
123+
//
124+
// BipartiteGraph encoding (left_size = 2, right_size = 2):
125+
// left local 0 -> v0, local 1 -> v2
126+
// right local 0 -> v1, local 1 -> v3
127+
// edges (left_idx, right_idx):
128+
// (v0, v1) -> (0, 0)
129+
// (v1, v2) -> (1, 0) (v2 is left=1, v1 is right=0)
130+
// (v2, v3) -> (1, 1)
131+
//
132+
// Unified vertex labels:
133+
// 0 = v0 (left 0)
134+
// 1 = v2 (left 1)
135+
// 2 = v1 (right 0)
136+
// 3 = v3 (right 1)
137+
//
138+
// Unified edges from Graph::edges():
139+
// (0, 2), (1, 2), (1, 3)
140+
//
141+
// mm(G) = 1, achieved by selecting the middle edge (v1, v2),
142+
// which is unified edge index 1 (i.e., (1, 2)).
143+
// So source_config = [0, 1, 0].
144+
//
145+
// complement(G) edges: (0, 1), (0, 3), (2, 3).
146+
//
147+
// Achromatic 3-coloring of complement(G):
148+
// v0 (idx 0) -> color 0
149+
// v2 (idx 1) -> color 1
150+
// v1 (idx 2) -> color 1 (paired with v2 = G-edge (v1, v2))
151+
// v3 (idx 3) -> color 2
152+
// target_config = [0, 1, 1, 2].
153+
let source = MinimumMaximalMatching::new(BipartiteGraph::new(
154+
2,
155+
2,
156+
vec![(0, 0), (1, 0), (1, 1)],
157+
));
158+
crate::example_db::specs::rule_example_with_witness::<
159+
_,
160+
MaximumAchromaticNumber<SimpleGraph>,
161+
>(
162+
source,
163+
SolutionPair {
164+
source_config: vec![0, 1, 0],
165+
target_config: vec![0, 1, 1, 2],
166+
},
167+
)
168+
},
169+
}]
170+
}
171+
172+
#[cfg(test)]
173+
#[path = "../unit_tests/rules/minimummaximalmatching_maximumachromaticnumber.rs"]
174+
mod tests;

src/rules/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub(crate) mod minimumcoveringbycliques_minimumintersectiongraphbasis;
8888
pub(crate) mod minimumdiscreteplanarinversekinematics_qubo;
8989
pub(crate) mod minimumfeedbackarcset_maximumlikelihoodranking;
9090
pub(crate) mod minimumfeedbackvertexset_minimumcodegenerationunlimitedregisters;
91+
pub(crate) mod minimummaximalmatching_maximumachromaticnumber;
9192
pub(crate) mod minimummultiwaycut_qubo;
9293
pub(crate) mod minimumvertexcover_comparativecontainment;
9394
pub(crate) mod minimumvertexcover_ensemblecomputation;
@@ -506,6 +507,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
506507
);
507508
specs.extend(minimumvertexcover_longestcommonsubsequence::canonical_rule_example_specs());
508509
specs.extend(minimumvertexcover_maximumindependentset::canonical_rule_example_specs());
510+
specs.extend(minimummaximalmatching_maximumachromaticnumber::canonical_rule_example_specs());
509511
specs.extend(minimumvertexcover_minimummaximalmatching::canonical_rule_example_specs());
510512
specs.extend(minimumvertexcover_minimumfeedbackarcset::canonical_rule_example_specs());
511513
specs.extend(minimumvertexcover_minimumfeedbackvertexset::canonical_rule_example_specs());

0 commit comments

Comments
 (0)