problemreductions/models/graph/
maximum_matching.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Max, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "MaximumMatching",
17 display_name: "Maximum Matching",
18 aliases: &["MaxMatching"],
19 dimensions: &[
20 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21 VariantDimension::new("weight", "i64", &["i64"]),
22 ],
23 category: crate::registry::ProblemCategory::Graph,
24 module_path: module_path!(),
25 description: "Find maximum weight matching in a graph",
26 fields: MaximumMatchingCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(try_from = "MaximumMatchingData<G, W>")]
61#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: Clone + Default + Deserialize<'de>"))]
62pub struct MaximumMatching<G, W> {
63 graph: G,
65 edge_weights: Vec<W>,
67}
68
69#[derive(Deserialize)]
70struct MaximumMatchingData<G, W> {
71 graph: G,
72 edge_weights: Vec<W>,
73}
74
75impl<G, W> TryFrom<MaximumMatchingData<G, W>> for MaximumMatching<G, W>
76where
77 G: Graph,
78 W: Clone + Default,
79{
80 type Error = crate::registry::ConstructionError;
81
82 fn try_from(data: MaximumMatchingData<G, W>) -> Result<Self, Self::Error> {
83 Self::try_new(data.graph, data.edge_weights)
84 }
85}
86
87#[derive(Debug, Deserialize, crate::CreateSpec)]
88struct MaximumMatchingCreateSpec {
89 #[create(codec = "edge-list")]
90 graph: Vec<(usize, usize)>,
91 num_vertices: Option<usize>,
92 #[create(codec = "comma-separated")]
93 edge_weights: Option<Vec<i64>>,
94}
95
96impl TryFrom<MaximumMatchingCreateSpec> for MaximumMatching<SimpleGraph, i64> {
97 type Error = crate::registry::ConstructionError;
98
99 fn try_from(spec: MaximumMatchingCreateSpec) -> Result<Self, Self::Error> {
100 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
101 let edge_weights = spec
102 .edge_weights
103 .unwrap_or_else(|| vec![1; graph.num_edges()]);
104 Self::try_new(graph, edge_weights)
105 }
106}
107
108fn simple_graph_from_create(
109 edges: Vec<(usize, usize)>,
110 num_vertices: Option<usize>,
111) -> Result<SimpleGraph, crate::registry::ConstructionError> {
112 if edges.is_empty() && num_vertices.is_none() {
113 return Err("num_vertices is required for an empty graph"
114 .to_string()
115 .into());
116 }
117 for (index, &(u, v)) in edges.iter().enumerate() {
118 if u == v {
119 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
120 }
121 }
122 let inferred = edges
123 .iter()
124 .flat_map(|&(u, v)| [u, v])
125 .max()
126 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
127 .transpose()?
128 .unwrap_or(0);
129 let num_vertices = num_vertices.unwrap_or(inferred);
130 if num_vertices < inferred {
131 return Err(format!(
132 "num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}"
133 )
134 .into());
135 }
136 Ok(SimpleGraph::new(num_vertices, edges))
137}
138
139impl<G: Graph, W: Clone + Default> MaximumMatching<G, W> {
140 pub fn new(graph: G, edge_weights: Vec<W>) -> Self {
146 Self::try_new(graph, edge_weights).unwrap_or_else(|error| panic!("{error}"))
147 }
148
149 fn try_new(graph: G, edge_weights: Vec<W>) -> Result<Self, crate::registry::ConstructionError> {
150 Self::check_weights(&graph, &edge_weights)?;
151 Ok(Self {
152 graph,
153 edge_weights,
154 })
155 }
156
157 pub fn unit_weights(graph: G) -> Self
159 where
160 W: WeightElement,
161 {
162 let edge_weights = vec![W::unit(); graph.num_edges()];
163 Self {
164 graph,
165 edge_weights,
166 }
167 }
168
169 pub fn graph(&self) -> &G {
171 &self.graph
172 }
173
174 pub fn edge_endpoints(&self, edge_idx: usize) -> Option<(usize, usize)> {
176 self.graph.edges().get(edge_idx).copied()
177 }
178
179 pub fn edges(&self) -> Vec<(usize, usize, W)> {
181 self.graph
182 .edges()
183 .into_iter()
184 .zip(self.edge_weights.iter().cloned())
185 .map(|((u, v), w)| (u, v, w))
186 .collect()
187 }
188
189 pub fn vertex_to_edges(&self) -> HashMap<usize, Vec<usize>> {
191 let mut v2e: HashMap<usize, Vec<usize>> = HashMap::new();
192 for (idx, (u, v)) in self.graph.edges().iter().enumerate() {
193 v2e.entry(*u).or_default().push(idx);
194 v2e.entry(*v).or_default().push(idx);
195 }
196 v2e
197 }
198
199 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
201 self.is_valid_matching(config)
202 }
203
204 fn is_valid_matching(&self, config: &[bool]) -> bool {
206 let mut vertex_used = vec![false; self.graph.num_vertices()];
207
208 for (idx, &selected) in config.iter().enumerate() {
209 if selected {
210 if let Some((u, v)) = self.edge_endpoints(idx) {
211 if vertex_used[u] || vertex_used[v] {
212 return false;
213 }
214 vertex_used[u] = true;
215 vertex_used[v] = true;
216 }
217 }
218 }
219 true
220 }
221
222 pub fn set_weights(&mut self, weights: Vec<W>) {
224 Self::check_weights(&self.graph, &weights).unwrap_or_else(|error| panic!("{error}"));
225 self.edge_weights = weights;
226 }
227
228 fn check_weights(
229 graph: &G,
230 edge_weights: &[W],
231 ) -> Result<(), crate::registry::ConstructionError> {
232 if edge_weights.len() != graph.num_edges() {
233 return Err(crate::registry::ConstructionError::length_mismatch(
234 "edge_weights",
235 edge_weights.len(),
236 graph.num_edges(),
237 ));
238 }
239 Ok(())
240 }
241
242 pub fn weights(&self) -> Vec<W> {
244 self.edge_weights.clone()
245 }
246
247 pub fn is_weighted(&self) -> bool
249 where
250 W: WeightElement,
251 {
252 !W::IS_UNIT
253 }
254}
255
256impl<G: Graph, W: WeightElement> MaximumMatching<G, W> {
257 pub fn num_vertices(&self) -> usize {
259 self.graph().num_vertices()
260 }
261
262 pub fn num_edges(&self) -> usize {
264 self.graph().num_edges()
265 }
266}
267
268impl<G, W> Problem for MaximumMatching<G, W>
269where
270 G: Graph + crate::variant::VariantParam,
271 W: WeightElement + crate::variant::VariantParam,
272{
273 const NAME: &'static str = "MaximumMatching";
274 type Solution = Vec<bool>;
275 type Value = Max<W::Sum>;
276
277 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
278
279 fn variant() -> Vec<(&'static str, &'static str)> {
280 crate::variant_params![G, W]
281 }
282
283 fn evaluate(
284 &self,
285 config: &Self::Solution,
286 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
287 if config.len() != self.graph.num_edges() {
288 return Err(crate::traits::EvaluationError::InvalidConfiguration(
289 "edge-selection length does not match the graph".into(),
290 ));
291 }
292 Ok({
293 if !self.is_valid_matching(config) {
294 return Ok(Max(None));
295 }
296 let mut total = W::Sum::zero();
297 for (idx, &selected) in config.iter().enumerate() {
298 if selected {
299 if let Some(w) = self.edge_weights.get(idx) {
300 total = W::checked_add_to_sum(
301 total,
302 w.to_sum(),
303 "summing selected matching-edge weights",
304 )?;
305 }
306 }
307 }
308 Max(Some(total))
309 })
310 }
311}
312
313impl<G, W> crate::solvers::BruteForceProblem for MaximumMatching<G, W>
314where
315 G: Graph + crate::variant::VariantParam,
316 W: WeightElement + crate::variant::VariantParam,
317{
318 fn dimensions(&self) -> Vec<usize> {
319 vec![2; self.graph.num_edges()]
320 }
321}
322
323crate::impl_random_generate!(MaximumMatching<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
324 let graph = spec.graph()?;
325 let weights = vec![1; graph.num_edges()];
326 Ok(MaximumMatching::new(graph, weights))
327});
328
329crate::declare_variants! {
330 default MaximumMatching<SimpleGraph, i64> => "num_vertices^3" create MaximumMatchingCreateSpec random,
331}
332
333crate::register_brute_force! {
334 MaximumMatching<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
335}
336
337#[cfg(feature = "example-db")]
338pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
339 vec![crate::example_db::specs::ModelExampleSpec {
340 id: "maximum_matching_simplegraph",
341 instance: Box::new(MaximumMatching::<_, i64>::unit_weights(SimpleGraph::new(
342 5,
343 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
344 ))),
345 optimal_config: serde_json::json!(vec![true, false, false, false, true, false]),
346 optimal_value: serde_json::json!(2),
347 }]
348}
349
350#[cfg(test)]
355pub(crate) fn is_matching<G: Graph>(graph: &G, selected: &[bool]) -> bool {
356 assert_eq!(
357 selected.len(),
358 graph.num_edges(),
359 "selected length must match num_edges"
360 );
361
362 let edges = graph.edges();
363 let mut vertex_used = vec![false; graph.num_vertices()];
364 for (idx, &sel) in selected.iter().enumerate() {
365 if sel {
366 let (u, v) = edges[idx];
367 if vertex_used[u] || vertex_used[v] {
368 return false;
369 }
370 vertex_used[u] = true;
371 vertex_used[v] = true;
372 }
373 }
374 true
375}
376
377#[cfg(test)]
378#[path = "../../unit_tests/models/graph/maximum_matching.rs"]
379mod tests;