1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Max, One, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "MaxCut",
16 display_name: "Max Cut",
17 aliases: &["MaximumBipartiteSubgraph"],
18 dimensions: &[
19 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20 VariantDimension::new("weight", "i64", &["i64", "One"]),
21 ],
22 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Find maximum weight cut in a graph",
25 fields: MaxCutI64CreateSpec::FIELDS,
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct MaxCut<G, W> {
72 graph: G,
74 edge_weights: Vec<W>,
76}
77
78macro_rules! max_cut_create_spec {
79 ($name:ident, $weight:ty, $one:expr $(, $edge_weights:ident)?) => {
80 #[derive(Debug, Deserialize, crate::CreateSpec)]
81 struct $name {
82 #[create(codec = "edge-list")]
83 graph: Vec<(usize, usize)>,
84 num_vertices: Option<usize>,
85 $(
86 #[create(codec = "comma-separated")]
87 $edge_weights: Option<Vec<$weight>>,
88 )?
89 }
90
91 impl TryFrom<$name> for MaxCut<SimpleGraph, $weight> {
92 type Error = crate::registry::ConstructionError;
93
94 fn try_from(spec: $name) -> Result<Self, Self::Error> {
95 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
96 let edge_weights = { $(if let Some(value) = spec.$edge_weights { value } else)? { vec![$one; graph.num_edges()] } };
97 if edge_weights.len() != graph.num_edges() {
98 return Err(format!(
99 "edge_weights has length {}, expected {}",
100 edge_weights.len(),
101 graph.num_edges()
102 )
103 .into());
104 }
105 Ok(Self::new(graph, edge_weights))
106 }
107 }
108 };
109}
110
111max_cut_create_spec!(MaxCutI64CreateSpec, i64, 1, edge_weights);
112max_cut_create_spec!(MaxCutOneCreateSpec, One, One);
113
114fn simple_graph_from_create(
115 edges: Vec<(usize, usize)>,
116 num_vertices: Option<usize>,
117) -> Result<SimpleGraph, crate::registry::ConstructionError> {
118 if edges.is_empty() && num_vertices.is_none() {
119 return Err("num_vertices is required for an empty graph"
120 .to_string()
121 .into());
122 }
123 for (index, &(u, v)) in edges.iter().enumerate() {
124 if u == v {
125 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
126 }
127 }
128 let inferred = edges
129 .iter()
130 .flat_map(|&(u, v)| [u, v])
131 .max()
132 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
133 .transpose()?
134 .unwrap_or(0);
135 let num_vertices = num_vertices.unwrap_or(inferred);
136 if num_vertices < inferred {
137 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
138 }
139 Ok(SimpleGraph::new(num_vertices, edges))
140}
141
142impl<G: Graph, W: Clone + Default> MaxCut<G, W> {
143 pub fn new(graph: G, edge_weights: Vec<W>) -> Self {
149 assert_eq!(
150 edge_weights.len(),
151 graph.num_edges(),
152 "edge_weights length must match num_edges"
153 );
154 Self {
155 graph,
156 edge_weights,
157 }
158 }
159
160 pub fn unweighted(graph: G) -> Self
162 where
163 W: WeightElement,
164 {
165 let edge_weights = vec![W::unit(); graph.num_edges()];
166 Self {
167 graph,
168 edge_weights,
169 }
170 }
171
172 pub fn graph(&self) -> &G {
174 &self.graph
175 }
176
177 pub fn edges(&self) -> Vec<(usize, usize, W)> {
179 self.graph
180 .edges()
181 .into_iter()
182 .zip(self.edge_weights.iter())
183 .map(|((u, v), w)| (u, v, w.clone()))
184 .collect()
185 }
186
187 pub fn edge_weight_by_index(&self, idx: usize) -> Option<&W> {
189 self.edge_weights.get(idx)
190 }
191
192 pub fn edge_weight(&self, u: usize, v: usize) -> Option<&W> {
194 for (idx, (eu, ev)) in self.graph.edges().iter().enumerate() {
196 if (*eu == u && *ev == v) || (*eu == v && *ev == u) {
197 return self.edge_weights.get(idx);
198 }
199 }
200 None
201 }
202
203 pub fn edge_weights(&self) -> Vec<W> {
205 self.edge_weights.clone()
206 }
207
208 pub fn cut_size(&self, config: &[bool]) -> Result<W::Sum, crate::traits::EvaluationError>
210 where
211 W: WeightElement,
212 {
213 cut_size(&self.graph, &self.edge_weights, config)
214 }
215}
216
217impl<G: Graph, W: WeightElement> MaxCut<G, W> {
218 pub fn num_vertices(&self) -> usize {
220 self.graph().num_vertices()
221 }
222
223 pub fn num_edges(&self) -> usize {
225 self.graph().num_edges()
226 }
227}
228
229impl<G, W> Problem for MaxCut<G, W>
230where
231 G: Graph + crate::variant::VariantParam,
232 W: WeightElement + crate::variant::VariantParam,
233{
234 const NAME: &'static str = "MaxCut";
235 type Solution = Vec<bool>;
236 type Value = Max<W::Sum>;
237
238 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
239
240 fn variant() -> Vec<(&'static str, &'static str)> {
241 crate::variant_params![G, W]
242 }
243
244 fn evaluate(
245 &self,
246 config: &Self::Solution,
247 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
248 if config.len() != self.graph.num_vertices() {
249 return Err(crate::traits::EvaluationError::InvalidConfiguration(
250 "cut assignment length does not match the graph vertices".into(),
251 ));
252 }
253 Ok({
254 Max(Some(cut_size(&self.graph, &self.edge_weights, config)?))
256 })
257 }
258}
259
260impl<G, W> crate::solvers::BruteForceProblem for MaxCut<G, W>
261where
262 G: Graph + crate::variant::VariantParam,
263 W: WeightElement + crate::variant::VariantParam,
264{
265 fn dimensions(&self) -> Vec<usize> {
266 vec![2; self.graph.num_vertices()]
267 }
268}
269
270pub(crate) fn cut_size<G, W>(
277 graph: &G,
278 edge_weights: &[W],
279 partition: &[bool],
280) -> Result<W::Sum, crate::traits::EvaluationError>
281where
282 G: Graph,
283 W: WeightElement,
284{
285 let mut total = W::Sum::zero();
286 for ((u, v), weight) in graph.edges().iter().zip(edge_weights.iter()) {
287 if *u < partition.len() && *v < partition.len() && partition[*u] != partition[*v] {
288 total = W::checked_add_to_sum(total, weight.to_sum(), "summing cut-edge weights")?;
289 }
290 }
291 Ok(total)
292}
293
294crate::impl_random_generate!(MaxCut<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
295 let graph = spec.graph()?;
296 let weights = vec![1; graph.num_edges()];
297 Ok(MaxCut::new(graph, weights))
298});
299
300crate::declare_variants! {
301 default MaxCut<SimpleGraph, i64> => "2^(2.372 * num_vertices / 3)" create MaxCutI64CreateSpec random,
302 MaxCut<SimpleGraph, One> => "2^(0.7907 * num_vertices)" create MaxCutOneCreateSpec,
303}
304
305crate::register_brute_force! {
306 MaxCut<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
307 MaxCut<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
308}
309
310#[cfg(feature = "example-db")]
311pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
312 vec![
313 crate::example_db::specs::ModelExampleSpec {
314 id: "max_cut_simplegraph",
315 instance: Box::new(MaxCut::<_, i64>::unweighted(SimpleGraph::new(
316 5,
317 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
318 ))),
319 optimal_config: serde_json::json!(vec![true, false, false, true, false]),
320 optimal_value: serde_json::json!(5),
321 },
322 crate::example_db::specs::ModelExampleSpec {
323 id: "max_cut_seven_edge_graph",
324 instance: Box::new(MaxCut::new(
325 SimpleGraph::new(
326 5,
327 vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 4), (2, 3), (3, 4)],
328 ),
329 vec![One; 7],
330 )),
331 optimal_config: serde_json::json!(vec![false, true, false, true, false]),
332 optimal_value: serde_json::json!(6),
333 },
334 ]
335}
336
337#[cfg(test)]
338#[path = "../../unit_tests/models/graph/max_cut.rs"]
339mod tests;