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 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#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
59pub struct PartitionIntoPerfectMatchings<G> {
60 graph: G,
62 num_matchings: usize,
64}
65
66impl<G: Graph> PartitionIntoPerfectMatchings<G> {
67 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 pub fn graph(&self) -> &G {
85 &self.graph
86 }
87
88 pub fn num_matchings(&self) -> usize {
90 self.num_matchings
91 }
92
93 pub fn num_vertices(&self) -> usize {
95 self.graph.num_vertices()
96 }
97
98 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
128fn 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 if config.len() != n {
138 return false;
139 }
140 if config.iter().any(|&c| c >= num_matchings) {
141 return false;
142 }
143
144 for group in 0..num_matchings {
147 let members: Vec<usize> = (0..n).filter(|&v| config[v] == group).collect();
148 if members.is_empty() {
150 continue;
151 }
152 if !members.len().is_multiple_of(2) {
154 return false;
155 }
156 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;