problemreductions/models/graph/
maximum_matching.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::{OptimizationProblem, Problem};
9use crate::types::{Direction, SolutionSize, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "MaximumMatching",
17 module_path: module_path!(),
18 description: "Find maximum weight matching in a graph",
19 fields: &[
20 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
21 FieldInfo { name: "edge_weights", type_name: "Vec<W>", description: "Edge weights w: E -> R" },
22 ],
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct MaximumMatching<G, W> {
57 graph: G,
59 edge_weights: Vec<W>,
61}
62
63impl<G: Graph, W: Clone + Default> MaximumMatching<G, W> {
64 pub fn new(graph: G, edge_weights: Vec<W>) -> Self {
70 assert_eq!(
71 edge_weights.len(),
72 graph.num_edges(),
73 "edge_weights length must match num_edges"
74 );
75 Self {
76 graph,
77 edge_weights,
78 }
79 }
80
81 pub fn unit_weights(graph: G) -> Self
83 where
84 W: From<i32>,
85 {
86 let edge_weights = vec![W::from(1); graph.num_edges()];
87 Self {
88 graph,
89 edge_weights,
90 }
91 }
92
93 pub fn graph(&self) -> &G {
95 &self.graph
96 }
97
98 pub fn edge_endpoints(&self, edge_idx: usize) -> Option<(usize, usize)> {
100 self.graph.edges().get(edge_idx).copied()
101 }
102
103 pub fn edges(&self) -> Vec<(usize, usize, W)> {
105 self.graph
106 .edges()
107 .into_iter()
108 .zip(self.edge_weights.iter().cloned())
109 .map(|((u, v), w)| (u, v, w))
110 .collect()
111 }
112
113 pub fn vertex_to_edges(&self) -> HashMap<usize, Vec<usize>> {
115 let mut v2e: HashMap<usize, Vec<usize>> = HashMap::new();
116 for (idx, (u, v)) in self.graph.edges().iter().enumerate() {
117 v2e.entry(*u).or_default().push(idx);
118 v2e.entry(*v).or_default().push(idx);
119 }
120 v2e
121 }
122
123 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
125 self.is_valid_matching(config)
126 }
127
128 fn is_valid_matching(&self, config: &[usize]) -> bool {
130 let mut vertex_used = vec![false; self.graph.num_vertices()];
131
132 for (idx, &selected) in config.iter().enumerate() {
133 if selected == 1 {
134 if let Some((u, v)) = self.edge_endpoints(idx) {
135 if vertex_used[u] || vertex_used[v] {
136 return false;
137 }
138 vertex_used[u] = true;
139 vertex_used[v] = true;
140 }
141 }
142 }
143 true
144 }
145
146 pub fn set_weights(&mut self, weights: Vec<W>) {
148 assert_eq!(weights.len(), self.graph.num_edges());
149 self.edge_weights = weights;
150 }
151
152 pub fn weights(&self) -> Vec<W> {
154 self.edge_weights.clone()
155 }
156
157 pub fn is_weighted(&self) -> bool
159 where
160 W: WeightElement,
161 {
162 !W::IS_UNIT
163 }
164}
165
166impl<G: Graph, W: WeightElement> MaximumMatching<G, W> {
167 pub fn num_vertices(&self) -> usize {
169 self.graph().num_vertices()
170 }
171
172 pub fn num_edges(&self) -> usize {
174 self.graph().num_edges()
175 }
176}
177
178impl<G, W> Problem for MaximumMatching<G, W>
179where
180 G: Graph + crate::variant::VariantParam,
181 W: WeightElement + crate::variant::VariantParam,
182{
183 const NAME: &'static str = "MaximumMatching";
184 type Metric = SolutionSize<W::Sum>;
185
186 fn variant() -> Vec<(&'static str, &'static str)> {
187 crate::variant_params![G, W]
188 }
189
190 fn dims(&self) -> Vec<usize> {
191 vec![2; self.graph.num_edges()]
192 }
193
194 fn evaluate(&self, config: &[usize]) -> SolutionSize<W::Sum> {
195 if !self.is_valid_matching(config) {
196 return SolutionSize::Invalid;
197 }
198 let mut total = W::Sum::zero();
199 for (idx, &selected) in config.iter().enumerate() {
200 if selected == 1 {
201 if let Some(w) = self.edge_weights.get(idx) {
202 total += w.to_sum();
203 }
204 }
205 }
206 SolutionSize::Valid(total)
207 }
208}
209
210impl<G, W> OptimizationProblem for MaximumMatching<G, W>
211where
212 G: Graph + crate::variant::VariantParam,
213 W: WeightElement + crate::variant::VariantParam,
214{
215 type Value = W::Sum;
216
217 fn direction(&self) -> Direction {
218 Direction::Maximize
219 }
220}
221
222crate::declare_variants! {
223 MaximumMatching<SimpleGraph, i32> => "num_vertices^3",
224}
225
226#[cfg(test)]
231pub(crate) fn is_matching<G: Graph>(graph: &G, selected: &[bool]) -> bool {
232 assert_eq!(
233 selected.len(),
234 graph.num_edges(),
235 "selected length must match num_edges"
236 );
237
238 let edges = graph.edges();
239 let mut vertex_used = vec![false; graph.num_vertices()];
240 for (idx, &sel) in selected.iter().enumerate() {
241 if sel {
242 let (u, v) = edges[idx];
243 if vertex_used[u] || vertex_used[v] {
244 return false;
245 }
246 vertex_used[u] = true;
247 vertex_used[v] = true;
248 }
249 }
250 true
251}
252
253#[cfg(test)]
254#[path = "../../unit_tests/models/graph/maximum_matching.rs"]
255mod tests;