Skip to main content

problemreductions/models/graph/
minimum_cut_into_bounded_sets.rs

1//! MinimumCutIntoBoundedSets problem implementation.
2//!
3//! A graph partitioning problem that finds a partition of vertices into two
4//! bounded-size sets (containing designated source and sink vertices) that
5//! minimizes total cut weight. From Garey & Johnson, A2 ND17.
6
7use crate::registry::{FieldInfo, 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", "i32", &["i32"]),
22        ],
23        module_path: module_path!(),
24        description: "Find a minimum-weight cut partitioning vertices into two bounded-size sets",
25        fields: &[
26            FieldInfo { name: "graph", type_name: "G", description: "The undirected graph G = (V, E)" },
27            FieldInfo { name: "edge_weights", type_name: "Vec<W>", description: "Edge weights w: E -> Z+" },
28            FieldInfo { name: "source", type_name: "usize", description: "Source vertex s (must be in V1)" },
29            FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t (must be in V2)" },
30            FieldInfo { name: "size_bound", type_name: "usize", description: "Maximum size B for each partition set" },
31        ],
32    }
33}
34
35/// Minimum Cut Into Bounded Sets (Garey & Johnson ND17).
36///
37/// Given a weighted graph G = (V, E), source vertex s, sink vertex t,
38/// and size bound B, find a partition of V into disjoint sets V1 and V2
39/// such that:
40/// - s is in V1, t is in V2
41/// - |V1| <= B, |V2| <= B
42/// - The total weight of edges crossing the partition is minimized
43///
44/// # Type Parameters
45///
46/// * `G` - The graph type (e.g., `SimpleGraph`)
47/// * `W` - The weight type for edges (e.g., `i32`)
48///
49/// # Example
50///
51/// ```
52/// use problemreductions::models::graph::MinimumCutIntoBoundedSets;
53/// use problemreductions::topology::SimpleGraph;
54/// use problemreductions::{Problem, Solver, BruteForce};
55///
56/// // Simple 4-vertex path graph with unit weights, s=0, t=3
57/// let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]);
58/// let problem = MinimumCutIntoBoundedSets::new(graph, vec![1, 1, 1], 0, 3, 3);
59///
60/// // Partition {0,1} vs {2,3}: cut edge (1,2) with weight 1
61/// let val = problem.evaluate(&[0, 0, 1, 1]);
62/// assert_eq!(val, problemreductions::types::Min(Some(1)));
63/// ```
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct MinimumCutIntoBoundedSets<G, W: WeightElement> {
66    /// The underlying graph structure.
67    graph: G,
68    /// Weights for each edge (in the same order as graph.edges()).
69    edge_weights: Vec<W>,
70    /// Source vertex s that must be in V1.
71    source: usize,
72    /// Sink vertex t that must be in V2.
73    sink: usize,
74    /// Maximum size B for each partition set.
75    size_bound: usize,
76}
77
78impl<G: Graph, W: WeightElement> MinimumCutIntoBoundedSets<G, W> {
79    /// Create a new MinimumCutIntoBoundedSets problem.
80    ///
81    /// # Arguments
82    /// * `graph` - The undirected graph
83    /// * `edge_weights` - Weights for each edge (must match graph.num_edges())
84    /// * `source` - Source vertex s (must be in V1)
85    /// * `sink` - Sink vertex t (must be in V2)
86    /// * `size_bound` - Maximum size B for each partition set
87    ///
88    /// # Panics
89    /// Panics if edge_weights length doesn't match num_edges, if source == sink,
90    /// or if source/sink are out of bounds.
91    pub fn new(
92        graph: G,
93        edge_weights: Vec<W>,
94        source: usize,
95        sink: usize,
96        size_bound: usize,
97    ) -> Self {
98        assert_eq!(
99            edge_weights.len(),
100            graph.num_edges(),
101            "edge_weights length must match num_edges"
102        );
103        assert!(source < graph.num_vertices(), "source vertex out of bounds");
104        assert!(sink < graph.num_vertices(), "sink vertex out of bounds");
105        assert_ne!(source, sink, "source and sink must be different vertices");
106        Self {
107            graph,
108            edge_weights,
109            source,
110            sink,
111            size_bound,
112        }
113    }
114
115    /// Get a reference to the underlying graph.
116    pub fn graph(&self) -> &G {
117        &self.graph
118    }
119
120    /// Get the edge weights.
121    pub fn edge_weights(&self) -> &[W] {
122        &self.edge_weights
123    }
124
125    /// Get the source vertex.
126    pub fn source(&self) -> usize {
127        self.source
128    }
129
130    /// Get the sink vertex.
131    pub fn sink(&self) -> usize {
132        self.sink
133    }
134
135    /// Get the size bound B.
136    pub fn size_bound(&self) -> usize {
137        self.size_bound
138    }
139
140    /// Get the number of vertices in the underlying graph.
141    pub fn num_vertices(&self) -> usize {
142        self.graph.num_vertices()
143    }
144
145    /// Get the number of edges in the underlying graph.
146    pub fn num_edges(&self) -> usize {
147        self.graph.num_edges()
148    }
149}
150
151impl<G, W> Problem for MinimumCutIntoBoundedSets<G, W>
152where
153    G: Graph + crate::variant::VariantParam,
154    W: WeightElement + crate::variant::VariantParam,
155{
156    const NAME: &'static str = "MinimumCutIntoBoundedSets";
157    type Value = Min<W::Sum>;
158
159    fn variant() -> Vec<(&'static str, &'static str)> {
160        crate::variant_params![G, W]
161    }
162
163    fn dims(&self) -> Vec<usize> {
164        vec![2; self.graph.num_vertices()]
165    }
166
167    fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
168        let n = self.graph.num_vertices();
169        if config.len() != n {
170            return Min(None);
171        }
172
173        // Check source is in V1 (config=0) and sink is in V2 (config=1)
174        if config[self.source] != 0 || config[self.sink] != 1 {
175            return Min(None);
176        }
177
178        // Check size bounds
179        let count_v1 = config.iter().filter(|&&x| x == 0).count();
180        let count_v2 = config.iter().filter(|&&x| x == 1).count();
181        if count_v1 > self.size_bound || count_v2 > self.size_bound {
182            return Min(None);
183        }
184
185        // Compute cut weight
186        let mut cut_weight = W::Sum::zero();
187        for ((u, v), weight) in self.graph.edges().iter().zip(self.edge_weights.iter()) {
188            if config[*u] != config[*v] {
189                cut_weight += weight.to_sum();
190            }
191        }
192
193        Min(Some(cut_weight))
194    }
195}
196
197#[cfg(feature = "example-db")]
198pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
199    vec![crate::example_db::specs::ModelExampleSpec {
200        id: "minimum_cut_into_bounded_sets_i32",
201        instance: Box::new(MinimumCutIntoBoundedSets::new(
202            SimpleGraph::new(
203                8,
204                vec![
205                    (0, 1),
206                    (0, 2),
207                    (1, 2),
208                    (1, 3),
209                    (2, 4),
210                    (3, 5),
211                    (3, 6),
212                    (4, 5),
213                    (4, 6),
214                    (5, 7),
215                    (6, 7),
216                    (5, 6),
217                ],
218            ),
219            vec![2, 3, 1, 4, 2, 1, 3, 2, 1, 2, 3, 1],
220            0,
221            7,
222            5,
223        )),
224        // V1={0,1,2,3}, V2={4,5,6,7}: cut edges (2,4)=2,(3,5)=1,(3,6)=3 => 6
225        optimal_config: vec![0, 0, 0, 0, 1, 1, 1, 1],
226        optimal_value: serde_json::json!(6),
227    }]
228}
229
230crate::declare_variants! {
231    default MinimumCutIntoBoundedSets<SimpleGraph, i32> => "2^num_vertices",
232}
233
234#[cfg(test)]
235#[path = "../../unit_tests/models/graph/minimum_cut_into_bounded_sets.rs"]
236mod tests;