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)]
71#[serde(try_from = "MaxCutData<G, W>")]
72#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: Clone + Default + Deserialize<'de>"))]
73pub struct MaxCut<G, W> {
74 graph: G,
76 edge_weights: Vec<W>,
78}
79
80#[derive(Deserialize)]
81struct MaxCutData<G, W> {
82 graph: G,
83 edge_weights: Vec<W>,
84}
85
86impl<G, W> TryFrom<MaxCutData<G, W>> for MaxCut<G, W>
87where
88 G: Graph,
89 W: Clone + Default,
90{
91 type Error = crate::registry::ConstructionError;
92
93 fn try_from(data: MaxCutData<G, W>) -> Result<Self, Self::Error> {
94 Self::try_new(data.graph, data.edge_weights)
95 }
96}
97
98macro_rules! max_cut_create_spec {
99 ($name:ident, $weight:ty, $one:expr $(, $edge_weights:ident)?) => {
100 #[derive(Debug, Deserialize, crate::CreateSpec)]
101 struct $name {
102 #[create(codec = "edge-list")]
103 graph: Vec<(usize, usize)>,
104 num_vertices: Option<usize>,
105 $(
106 #[create(codec = "comma-separated")]
107 $edge_weights: Option<Vec<$weight>>,
108 )?
109 }
110
111 impl TryFrom<$name> for MaxCut<SimpleGraph, $weight> {
112 type Error = crate::registry::ConstructionError;
113
114 fn try_from(spec: $name) -> Result<Self, Self::Error> {
115 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
116 let edge_weights = { $(if let Some(value) = spec.$edge_weights { value } else)? { vec![$one; graph.num_edges()] } };
117 Self::try_new(graph, edge_weights)
118 }
119 }
120 };
121}
122
123max_cut_create_spec!(MaxCutI64CreateSpec, i64, 1, edge_weights);
124max_cut_create_spec!(MaxCutOneCreateSpec, One, One);
125
126fn simple_graph_from_create(
127 edges: Vec<(usize, usize)>,
128 num_vertices: Option<usize>,
129) -> Result<SimpleGraph, crate::registry::ConstructionError> {
130 if edges.is_empty() && num_vertices.is_none() {
131 return Err("num_vertices is required for an empty graph"
132 .to_string()
133 .into());
134 }
135 for (index, &(u, v)) in edges.iter().enumerate() {
136 if u == v {
137 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
138 }
139 }
140 let inferred = edges
141 .iter()
142 .flat_map(|&(u, v)| [u, v])
143 .max()
144 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
145 .transpose()?
146 .unwrap_or(0);
147 let num_vertices = num_vertices.unwrap_or(inferred);
148 if num_vertices < inferred {
149 return Err(format!("num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}").into());
150 }
151 Ok(SimpleGraph::new(num_vertices, edges))
152}
153
154impl<G: Graph, W: Clone + Default> MaxCut<G, W> {
155 pub fn new(graph: G, edge_weights: Vec<W>) -> Self {
161 Self::try_new(graph, edge_weights).unwrap_or_else(|error| panic!("{error}"))
162 }
163
164 fn try_new(graph: G, edge_weights: Vec<W>) -> Result<Self, crate::registry::ConstructionError> {
165 if edge_weights.len() != graph.num_edges() {
166 return Err(crate::registry::ConstructionError::length_mismatch(
167 "edge_weights",
168 edge_weights.len(),
169 graph.num_edges(),
170 ));
171 }
172 Ok(Self {
173 graph,
174 edge_weights,
175 })
176 }
177
178 pub fn unweighted(graph: G) -> Self
180 where
181 W: WeightElement,
182 {
183 let edge_weights = vec![W::unit(); graph.num_edges()];
184 Self {
185 graph,
186 edge_weights,
187 }
188 }
189
190 pub fn graph(&self) -> &G {
192 &self.graph
193 }
194
195 pub fn edges(&self) -> Vec<(usize, usize, W)> {
197 self.graph
198 .edges()
199 .into_iter()
200 .zip(self.edge_weights.iter())
201 .map(|((u, v), w)| (u, v, w.clone()))
202 .collect()
203 }
204
205 pub fn edge_weight_by_index(&self, idx: usize) -> Option<&W> {
207 self.edge_weights.get(idx)
208 }
209
210 pub fn edge_weight(&self, u: usize, v: usize) -> Option<&W> {
212 for (idx, (eu, ev)) in self.graph.edges().iter().enumerate() {
214 if (*eu == u && *ev == v) || (*eu == v && *ev == u) {
215 return self.edge_weights.get(idx);
216 }
217 }
218 None
219 }
220
221 pub fn edge_weights(&self) -> Vec<W> {
223 self.edge_weights.clone()
224 }
225
226 pub fn cut_size(&self, config: &[bool]) -> Result<W::Sum, crate::traits::EvaluationError>
228 where
229 W: WeightElement,
230 {
231 cut_size(&self.graph, &self.edge_weights, config)
232 }
233}
234
235impl<G: Graph, W: WeightElement> MaxCut<G, W> {
236 pub fn num_vertices(&self) -> usize {
238 self.graph().num_vertices()
239 }
240
241 pub fn num_edges(&self) -> usize {
243 self.graph().num_edges()
244 }
245}
246
247impl<G, W> Problem for MaxCut<G, W>
248where
249 G: Graph + crate::variant::VariantParam,
250 W: WeightElement + crate::variant::VariantParam,
251{
252 const NAME: &'static str = "MaxCut";
253 type Solution = Vec<bool>;
254 type Value = Max<W::Sum>;
255
256 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
257
258 fn variant() -> Vec<(&'static str, &'static str)> {
259 crate::variant_params![G, W]
260 }
261
262 fn evaluate(
263 &self,
264 config: &Self::Solution,
265 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
266 if config.len() != self.graph.num_vertices() {
267 return Err(crate::traits::EvaluationError::InvalidConfiguration(
268 "cut assignment length does not match the graph vertices".into(),
269 ));
270 }
271 Ok({
272 Max(Some(cut_size(&self.graph, &self.edge_weights, config)?))
274 })
275 }
276}
277
278impl<G, W> crate::solvers::BruteForceProblem for MaxCut<G, W>
279where
280 G: Graph + crate::variant::VariantParam,
281 W: WeightElement + crate::variant::VariantParam,
282{
283 fn dimensions(&self) -> Vec<usize> {
284 vec![2; self.graph.num_vertices()]
285 }
286}
287
288pub(crate) fn cut_size<G, W>(
295 graph: &G,
296 edge_weights: &[W],
297 partition: &[bool],
298) -> Result<W::Sum, crate::traits::EvaluationError>
299where
300 G: Graph,
301 W: WeightElement,
302{
303 let mut total = W::Sum::zero();
304 for ((u, v), weight) in graph.edges().iter().zip(edge_weights.iter()) {
305 if *u < partition.len() && *v < partition.len() && partition[*u] != partition[*v] {
306 total = W::checked_add_to_sum(total, weight.to_sum(), "summing cut-edge weights")?;
307 }
308 }
309 Ok(total)
310}
311
312crate::impl_random_generate!(MaxCut<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
313 let graph = spec.graph()?;
314 let weights = vec![1; graph.num_edges()];
315 Ok(MaxCut::new(graph, weights))
316});
317
318crate::declare_variants! {
319 default MaxCut<SimpleGraph, i64> => "2^(2.372 * num_vertices / 3)" create MaxCutI64CreateSpec random,
320 MaxCut<SimpleGraph, One> => "2^(0.7907 * num_vertices)" create MaxCutOneCreateSpec,
321}
322
323crate::register_brute_force! {
324 MaxCut<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
325 MaxCut<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
326}
327
328#[cfg(feature = "example-db")]
329pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
330 vec![
331 crate::example_db::specs::ModelExampleSpec {
332 id: "max_cut_simplegraph",
333 instance: Box::new(MaxCut::<_, i64>::unweighted(SimpleGraph::new(
334 5,
335 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
336 ))),
337 optimal_config: serde_json::json!(vec![true, false, false, true, false]),
338 optimal_value: serde_json::json!(5),
339 },
340 crate::example_db::specs::ModelExampleSpec {
341 id: "max_cut_seven_edge_graph",
342 instance: Box::new(MaxCut::new(
343 SimpleGraph::new(
344 5,
345 vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 4), (2, 3), (3, 4)],
346 ),
347 vec![One; 7],
348 )),
349 optimal_config: serde_json::json!(vec![false, true, false, true, false]),
350 optimal_value: serde_json::json!(6),
351 },
352 ]
353}
354
355#[cfg(test)]
356#[path = "../../unit_tests/models/graph/max_cut.rs"]
357mod tests;
358
359crate::decision_problem_meta!(MaxCut<SimpleGraph, i64>, "DecisionMaxCut");
360crate::register_decision_variant!(
361 MaxCut<SimpleGraph, i64>, "DecisionMaxCut", "2^(2.372 * num_vertices / 3)", &[],
362 "Does a feasible solution have objective value >= the bound?",
363 category: crate::registry::ProblemCategory::Graph,
364 dims: [
365 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
366 VariantDimension::new("weight", "i64", &["i64"]),
367 ],
368 fields: [
369 crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Graph edges as comma-separated vertex pairs." },
370 crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices, including isolated vertices." },
371 crate::registry::FieldInfo { name: "edge_weights", type_name: "Vec<i64>", description: "Weights for each edge in graph order." },
372 crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values >= this bound" },
373 ],
374 decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
375);
376
377#[cfg(feature = "example-db")]
378pub(crate) fn decision_canonical_rule_example_specs(
379) -> Vec<crate::example_db::specs::RuleExampleSpec> {
380 vec![crate::example_db::specs::RuleExampleSpec {
381 id: "decision_max_cut_to_max_cut",
382 build: || {
383 let source = crate::models::decision::Decision::new(
384 MaxCut::<_, i64>::unweighted(SimpleGraph::new(
385 5,
386 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
387 )),
388 5,
389 );
390 let witness = serde_json::json!(vec![true, false, false, true, false]);
391 crate::example_db::specs::rule_example_with_witness::<_, MaxCut<SimpleGraph, i64>>(
392 source,
393 crate::export::SolutionPair {
394 source_config: witness.clone(),
395 target_config: witness,
396 },
397 )
398 },
399 }]
400}