problemreductions/models/graph/
partition_into_perfect_matchings.rs1use 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 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Partition vertices into K groups each inducing a perfect matching",
25 fields: &[
26 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
27 FieldInfo { name: "num_matchings", type_name: "usize", description: "num_matchings: maximum number of matching groups K (>= 1)" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
60pub struct PartitionIntoPerfectMatchings<G> {
61 graph: G,
63 num_matchings: usize,
65}
66
67impl<G: Graph> PartitionIntoPerfectMatchings<G> {
68 pub fn new(graph: G, num_matchings: usize) -> Self {
73 assert!(num_matchings >= 1, "num_matchings must be at least 1");
74 assert!(
75 num_matchings <= graph.num_vertices(),
76 "num_matchings must be at most num_vertices"
77 );
78 Self {
79 graph,
80 num_matchings,
81 }
82 }
83
84 pub fn graph(&self) -> &G {
86 &self.graph
87 }
88
89 pub fn num_matchings(&self) -> usize {
91 self.num_matchings
92 }
93
94 pub fn num_vertices(&self) -> usize {
96 self.graph.num_vertices()
97 }
98
99 pub fn num_edges(&self) -> usize {
101 self.graph.num_edges()
102 }
103}
104
105impl<G> Problem for PartitionIntoPerfectMatchings<G>
106where
107 G: Graph + VariantParam,
108{
109 const NAME: &'static str = "PartitionIntoPerfectMatchings";
110 type Solution = Vec<usize>;
111 type Value = crate::types::Or;
112
113 crate::problem_parameters![
114 ("num_edges", num_edges),
115 ("num_matchings", num_matchings),
116 ("num_vertices", num_vertices),
117 ];
118
119 fn variant() -> Vec<(&'static str, &'static str)> {
120 crate::variant_params![G]
121 }
122
123 fn evaluate(
124 &self,
125 config: &Self::Solution,
126 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
127 if config.len() != self.graph.num_vertices() {
128 return Err(crate::traits::EvaluationError::InvalidConfiguration(
129 "partition assignment length does not match the graph vertices".into(),
130 ));
131 }
132 if config.iter().any(|&part| part >= self.num_matchings) {
133 return Err(crate::traits::EvaluationError::InvalidConfiguration(
134 "partition assignment contains an out-of-range matching".into(),
135 ));
136 }
137 Ok({
138 crate::types::Or(is_valid_perfect_matching_partition(
139 &self.graph,
140 self.num_matchings,
141 config,
142 ))
143 })
144 }
145}
146
147impl<G> crate::solvers::BruteForceProblem for PartitionIntoPerfectMatchings<G>
148where
149 G: Graph + VariantParam,
150{
151 fn dimensions(&self) -> Vec<usize> {
152 vec![self.num_matchings; self.graph.num_vertices()]
153 }
154}
155
156fn is_valid_perfect_matching_partition<G: Graph>(
158 graph: &G,
159 num_matchings: usize,
160 config: &[usize],
161) -> bool {
162 let n = graph.num_vertices();
163
164 if config.len() != n {
166 return false;
167 }
168 if config.iter().any(|&c| c >= num_matchings) {
169 return false;
170 }
171
172 for group in 0..num_matchings {
175 let members: Vec<usize> = (0..n).filter(|&v| config[v] == group).collect();
176 if members.is_empty() {
178 continue;
179 }
180 if !members.len().is_multiple_of(2) {
182 return false;
183 }
184 for &v in &members {
186 let neighbor_count = members
187 .iter()
188 .filter(|&&u| u != v && graph.has_edge(v, u))
189 .count();
190 if neighbor_count != 1 {
191 return false;
192 }
193 }
194 }
195
196 true
197}
198
199crate::declare_variants! {
200 default PartitionIntoPerfectMatchings<SimpleGraph> => "num_matchings^num_vertices",
201}
202
203crate::register_brute_force! {
204 PartitionIntoPerfectMatchings<SimpleGraph>,
205}
206
207#[cfg(feature = "example-db")]
208pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
209 vec![crate::example_db::specs::ModelExampleSpec {
210 id: "partition_into_perfect_matchings_simplegraph",
211 instance: Box::new(PartitionIntoPerfectMatchings::new(
212 SimpleGraph::new(4, vec![(0, 1), (2, 3), (0, 2), (1, 3)]),
213 2,
214 )),
215 optimal_config: serde_json::json!(vec![0, 0, 1, 1]),
216 optimal_value: serde_json::json!(true),
217 }]
218}
219
220#[cfg(test)]
221#[path = "../../unit_tests/models/graph/partition_into_perfect_matchings.rs"]
222mod tests;