Skip to main content

problemreductions/models/graph/
partition_into_perfect_matchings.rs

1//! Partition Into Perfect Matchings problem implementation.
2//!
3//! Given a graph G = (V, E) and a positive integer K <= |V|, determine whether
4//! the vertex set can be partitioned into k <= K groups such that the subgraph
5//! induced by each group is a perfect matching (every vertex in the group has
6//! exactly one neighbor within the group).
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
9use crate::topology::{Graph, SimpleGraph};
10use crate::traits::Problem;
11use crate::variant::VariantParam;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "PartitionIntoPerfectMatchings",
17        display_name: "Partition into Perfect Matchings",
18        aliases: &[],
19        dimensions: &[
20            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21        ],
22        module_path: module_path!(),
23        description: "Partition vertices into K groups each inducing a perfect matching",
24        fields: &[
25            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
26            FieldInfo { name: "num_matchings", type_name: "usize", description: "num_matchings: maximum number of matching groups K (>= 1)" },
27        ],
28    }
29}
30
31/// The Partition Into Perfect Matchings problem.
32///
33/// Given a graph G = (V, E) and a positive integer K <= |V|, determine whether
34/// the vertices can be partitioned into k <= K groups V_1, ..., V_k such that
35/// the subgraph induced by each V_i is a perfect matching: every vertex in V_i
36/// has exactly one neighbor also in V_i.
37///
38/// # Type Parameters
39///
40/// * `G` - Graph type (e.g., SimpleGraph)
41///
42/// # Example
43///
44/// ```
45/// use problemreductions::models::graph::PartitionIntoPerfectMatchings;
46/// use problemreductions::topology::SimpleGraph;
47/// use problemreductions::{Problem, Solver, BruteForce};
48///
49/// // 4 vertices with edges: (0,1),(2,3),(0,2),(1,3)
50/// let graph = SimpleGraph::new(4, vec![(0,1),(2,3),(0,2),(1,3)]);
51/// let problem = PartitionIntoPerfectMatchings::new(graph, 2);
52///
53/// let solver = BruteForce::new();
54/// let solution = solver.find_witness(&problem);
55/// assert!(solution.is_some());
56/// ```
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
59pub struct PartitionIntoPerfectMatchings<G> {
60    /// The underlying graph.
61    graph: G,
62    /// Maximum number of matching groups.
63    num_matchings: usize,
64}
65
66impl<G: Graph> PartitionIntoPerfectMatchings<G> {
67    /// Create a new Partition Into Perfect Matchings instance.
68    ///
69    /// # Panics
70    /// Panics if `num_matchings` is zero or greater than `graph.num_vertices()`.
71    pub fn new(graph: G, num_matchings: usize) -> Self {
72        assert!(num_matchings >= 1, "num_matchings must be at least 1");
73        assert!(
74            num_matchings <= graph.num_vertices(),
75            "num_matchings must be at most num_vertices"
76        );
77        Self {
78            graph,
79            num_matchings,
80        }
81    }
82
83    /// Get a reference to the underlying graph.
84    pub fn graph(&self) -> &G {
85        &self.graph
86    }
87
88    /// Get the maximum number of matching groups.
89    pub fn num_matchings(&self) -> usize {
90        self.num_matchings
91    }
92
93    /// Get the number of vertices in the underlying graph.
94    pub fn num_vertices(&self) -> usize {
95        self.graph.num_vertices()
96    }
97
98    /// Get the number of edges in the underlying graph.
99    pub fn num_edges(&self) -> usize {
100        self.graph.num_edges()
101    }
102}
103
104impl<G> Problem for PartitionIntoPerfectMatchings<G>
105where
106    G: Graph + VariantParam,
107{
108    const NAME: &'static str = "PartitionIntoPerfectMatchings";
109    type Value = crate::types::Or;
110
111    fn variant() -> Vec<(&'static str, &'static str)> {
112        crate::variant_params![G]
113    }
114
115    fn dims(&self) -> Vec<usize> {
116        vec![self.num_matchings; self.graph.num_vertices()]
117    }
118
119    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
120        crate::types::Or(is_valid_perfect_matching_partition(
121            &self.graph,
122            self.num_matchings,
123            config,
124        ))
125    }
126}
127
128/// Check whether `config` is a valid K-perfect-matching partition of `graph`.
129fn is_valid_perfect_matching_partition<G: Graph>(
130    graph: &G,
131    num_matchings: usize,
132    config: &[usize],
133) -> bool {
134    let n = graph.num_vertices();
135
136    // Basic validity checks
137    if config.len() != n {
138        return false;
139    }
140    if config.iter().any(|&c| c >= num_matchings) {
141        return false;
142    }
143
144    // For each group, collect the vertices and check every vertex has exactly
145    // one neighbor within the group (i.e., the induced subgraph is a perfect matching).
146    for group in 0..num_matchings {
147        let members: Vec<usize> = (0..n).filter(|&v| config[v] == group).collect();
148        // Empty groups are OK
149        if members.is_empty() {
150            continue;
151        }
152        // A perfect matching requires an even number of vertices
153        if !members.len().is_multiple_of(2) {
154            return false;
155        }
156        // Each member must have exactly one neighbor in the group
157        for &v in &members {
158            let neighbor_count = members
159                .iter()
160                .filter(|&&u| u != v && graph.has_edge(v, u))
161                .count();
162            if neighbor_count != 1 {
163                return false;
164            }
165        }
166    }
167
168    true
169}
170
171crate::declare_variants! {
172    default PartitionIntoPerfectMatchings<SimpleGraph> => "num_matchings^num_vertices",
173}
174
175#[cfg(feature = "example-db")]
176pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
177    vec![crate::example_db::specs::ModelExampleSpec {
178        id: "partition_into_perfect_matchings_simplegraph",
179        instance: Box::new(PartitionIntoPerfectMatchings::new(
180            SimpleGraph::new(4, vec![(0, 1), (2, 3), (0, 2), (1, 3)]),
181            2,
182        )),
183        optimal_config: vec![0, 0, 1, 1],
184        optimal_value: serde_json::json!(true),
185    }]
186}
187
188#[cfg(test)]
189#[path = "../../unit_tests/models/graph/partition_into_perfect_matchings.rs"]
190mod tests;