1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::{Min, WeightElement};
11use num_traits::Zero;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "MinimumCutIntoBoundedSets",
17 display_name: "Minimum Cut Into Bounded Sets",
18 aliases: &[],
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 a minimum-weight cut partitioning vertices into two bounded-size sets",
26 fields: MinimumCutIntoBoundedSetsCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(try_from = "MinimumCutIntoBoundedSetsData<G, W>")]
61#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
62pub struct MinimumCutIntoBoundedSets<G, W: WeightElement> {
63 graph: G,
65 edge_weights: Vec<W>,
67 source: usize,
69 sink: usize,
71 size_bound: usize,
73}
74
75#[derive(Deserialize)]
76#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
77struct MinimumCutIntoBoundedSetsData<G, W: WeightElement> {
78 graph: G,
79 edge_weights: Vec<W>,
80 source: usize,
81 sink: usize,
82 size_bound: usize,
83}
84
85impl<G, W> TryFrom<MinimumCutIntoBoundedSetsData<G, W>> for MinimumCutIntoBoundedSets<G, W>
86where
87 G: Graph,
88 W: WeightElement,
89{
90 type Error = crate::registry::ConstructionError;
91
92 fn try_from(data: MinimumCutIntoBoundedSetsData<G, W>) -> Result<Self, Self::Error> {
93 Self::try_new(
94 data.graph,
95 data.edge_weights,
96 data.source,
97 data.sink,
98 data.size_bound,
99 )
100 }
101}
102
103#[derive(Debug, Deserialize, crate::CreateSpec)]
104struct MinimumCutIntoBoundedSetsCreateSpec {
105 graph: SimpleGraph,
107 edge_weights: Option<Vec<i64>>,
109 source: usize,
111 sink: usize,
113 size_bound: usize,
115}
116impl TryFrom<MinimumCutIntoBoundedSetsCreateSpec> for MinimumCutIntoBoundedSets<SimpleGraph, i64> {
117 type Error = crate::registry::ConstructionError;
118 fn try_from(spec: MinimumCutIntoBoundedSetsCreateSpec) -> Result<Self, Self::Error> {
119 let count = spec.graph.num_edges();
120 let edge_weights = spec.edge_weights.unwrap_or_else(|| vec![1; count]);
121 Self::try_new(
122 spec.graph,
123 edge_weights,
124 spec.source,
125 spec.sink,
126 spec.size_bound,
127 )
128 }
129}
130
131impl<G: Graph, W: WeightElement> MinimumCutIntoBoundedSets<G, W> {
132 pub fn new(
145 graph: G,
146 edge_weights: Vec<W>,
147 source: usize,
148 sink: usize,
149 size_bound: usize,
150 ) -> Self {
151 Self::try_new(graph, edge_weights, source, sink, size_bound)
152 .unwrap_or_else(|error| panic!("{error}"))
153 }
154
155 fn try_new(
156 graph: G,
157 edge_weights: Vec<W>,
158 source: usize,
159 sink: usize,
160 size_bound: usize,
161 ) -> Result<Self, crate::registry::ConstructionError> {
162 if edge_weights.len() != graph.num_edges() {
163 return Err(crate::registry::ConstructionError::length_mismatch(
164 "edge_weights",
165 edge_weights.len(),
166 graph.num_edges(),
167 ));
168 }
169 if source >= graph.num_vertices() {
170 return Err("source vertex out of bounds".into());
171 }
172 if sink >= graph.num_vertices() {
173 return Err("sink vertex out of bounds".into());
174 }
175 if source == sink {
176 return Err("source and sink must be different vertices".into());
177 }
178 Ok(Self {
179 graph,
180 edge_weights,
181 source,
182 sink,
183 size_bound,
184 })
185 }
186
187 pub fn graph(&self) -> &G {
189 &self.graph
190 }
191
192 pub fn edge_weights(&self) -> &[W] {
194 &self.edge_weights
195 }
196
197 pub fn source(&self) -> usize {
199 self.source
200 }
201
202 pub fn sink(&self) -> usize {
204 self.sink
205 }
206
207 pub fn size_bound(&self) -> usize {
209 self.size_bound
210 }
211
212 pub fn num_vertices(&self) -> usize {
214 self.graph.num_vertices()
215 }
216
217 pub fn num_edges(&self) -> usize {
219 self.graph.num_edges()
220 }
221}
222
223impl<G, W> Problem for MinimumCutIntoBoundedSets<G, W>
224where
225 G: Graph + crate::variant::VariantParam,
226 W: WeightElement + crate::variant::VariantParam,
227{
228 const NAME: &'static str = "MinimumCutIntoBoundedSets";
229 type Solution = Vec<bool>;
230 type Value = Min<W::Sum>;
231
232 crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
233
234 fn variant() -> Vec<(&'static str, &'static str)> {
235 crate::variant_params![G, W]
236 }
237
238 fn evaluate(
239 &self,
240 config: &Self::Solution,
241 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
242 Ok({
243 let n = self.graph.num_vertices();
244 if config.len() != n {
245 return Err(crate::traits::EvaluationError::InvalidConfiguration(
246 "partition assignment length does not match the graph vertices".into(),
247 ));
248 }
249
250 if config[self.source] || !config[self.sink] {
252 return Ok(Min(None));
253 }
254
255 let count_v1 = config.iter().filter(|&&x| !x).count();
257 let count_v2 = config.iter().filter(|&&x| x).count();
258 if count_v1 > self.size_bound || count_v2 > self.size_bound {
259 return Ok(Min(None));
260 }
261
262 let mut cut_weight = W::Sum::zero();
264 for ((u, v), weight) in self.graph.edges().iter().zip(self.edge_weights.iter()) {
265 if config[*u] != config[*v] {
266 cut_weight = W::checked_add_to_sum(
267 cut_weight,
268 weight.to_sum(),
269 "summing bounded-set cut weights",
270 )?;
271 }
272 }
273
274 Min(Some(cut_weight))
275 })
276 }
277}
278
279impl<G, W> crate::solvers::BruteForceProblem for MinimumCutIntoBoundedSets<G, W>
280where
281 G: Graph + crate::variant::VariantParam,
282 W: WeightElement + crate::variant::VariantParam,
283{
284 fn dimensions(&self) -> Vec<usize> {
285 vec![2; self.graph.num_vertices()]
286 }
287}
288
289#[cfg(feature = "example-db")]
290pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
291 vec![crate::example_db::specs::ModelExampleSpec {
292 id: "minimum_cut_into_bounded_sets",
293 instance: Box::new(MinimumCutIntoBoundedSets::new(
294 SimpleGraph::new(
295 8,
296 vec![
297 (0, 1),
298 (0, 2),
299 (1, 2),
300 (1, 3),
301 (2, 4),
302 (3, 5),
303 (3, 6),
304 (4, 5),
305 (4, 6),
306 (5, 7),
307 (6, 7),
308 (5, 6),
309 ],
310 ),
311 vec![2, 3, 1, 4, 2, 1, 3, 2, 1, 2, 3, 1],
312 0,
313 7,
314 5,
315 )),
316 optimal_config: serde_json::json!(vec![false, false, false, false, true, true, true, true]),
318 optimal_value: serde_json::json!(6),
319 }]
320}
321
322crate::impl_random_generate!(MinimumCutIntoBoundedSets<SimpleGraph, i64>, crate::random::EndpointRandomSpec, |spec| {
323 let (source, sink) = spec.endpoints()?;
324 let graph = spec.graph()?;
325 let edge_weights = vec![1; graph.num_edges()];
326 Ok(MinimumCutIntoBoundedSets::new(graph, edge_weights, source, sink, spec.num_vertices))
327});
328
329crate::declare_variants! {
330 default MinimumCutIntoBoundedSets<SimpleGraph, i64> => "2^num_vertices" create MinimumCutIntoBoundedSetsCreateSpec random,
331}
332
333crate::register_brute_force! {
334 MinimumCutIntoBoundedSets<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
335}
336
337#[cfg(test)]
338#[path = "../../unit_tests/models/graph/minimum_cut_into_bounded_sets.rs"]
339mod tests;