Skip to main content

problemreductions/models/misc/
optimum_communication_spanning_tree.rs

1//! Optimum Communication Spanning Tree problem implementation.
2//!
3//! Given a complete graph K_n with edge weights w(e) and communication
4//! requirements r(u,v) for each vertex pair, find a spanning tree T that
5//! minimizes the total communication cost: sum_{u<v} r(u,v) * W_T(u,v),
6//! where W_T(u,v) is the sum of edge weights on the unique path from u to v in T.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "OptimumCommunicationSpanningTree",
17        display_name: "Optimum Communication Spanning Tree",
18        aliases: &["OCST"],
19        dimensions: &[],
20        module_path: module_path!(),
21        description: "Find spanning tree minimizing total weighted communication cost",
22        fields: &[
23            FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices n" },
24            FieldInfo { name: "edge_weights", type_name: "Vec<Vec<i32>>", description: "Symmetric weight matrix w(i,j)" },
25            FieldInfo { name: "requirements", type_name: "Vec<Vec<i32>>", description: "Symmetric requirement matrix r(i,j)" },
26        ],
27    }
28}
29
30/// The Optimum Communication Spanning Tree problem.
31///
32/// Given a complete graph K_n with edge weights w(e) >= 0 and communication
33/// requirements r(u,v) >= 0 for each vertex pair, find a spanning tree T
34/// minimizing the total communication cost:
35///
36///   sum_{u < v} r(u,v) * W_T(u,v)
37///
38/// where W_T(u,v) is the weight of the unique path between u and v in T.
39///
40/// # Representation
41///
42/// Each edge of K_n is assigned a binary variable (0 = not in tree, 1 = in tree).
43/// Edges are ordered lexicographically: (0,1), (0,2), ..., (0,n-1), (1,2), ..., (n-2,n-1).
44/// A valid spanning tree has exactly n-1 selected edges forming a connected subgraph.
45///
46/// # Example
47///
48/// ```
49/// use problemreductions::models::misc::OptimumCommunicationSpanningTree;
50/// use problemreductions::{Problem, Solver, BruteForce};
51///
52/// let problem = OptimumCommunicationSpanningTree::new(
53///     vec![
54///         vec![0, 1, 2],
55///         vec![1, 0, 3],
56///         vec![2, 3, 0],
57///     ],
58///     vec![
59///         vec![0, 1, 1],
60///         vec![1, 0, 1],
61///         vec![1, 1, 0],
62///     ],
63/// );
64/// let solver = BruteForce::new();
65/// let solution = solver.find_witness(&problem);
66/// assert!(solution.is_some());
67/// ```
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct OptimumCommunicationSpanningTree {
70    num_vertices: usize,
71    edge_weights: Vec<Vec<i32>>,
72    requirements: Vec<Vec<i32>>,
73}
74
75impl OptimumCommunicationSpanningTree {
76    /// Create a new OptimumCommunicationSpanningTree instance.
77    ///
78    /// # Arguments
79    ///
80    /// * `edge_weights` - Symmetric n x n matrix with w(i,i) = 0 and w(i,j) >= 0.
81    /// * `requirements` - Symmetric n x n matrix with r(i,i) = 0 and r(i,j) >= 0.
82    ///
83    /// # Panics
84    ///
85    /// Panics if the matrices are not square, not the same size, have nonzero
86    /// diagonals, are not symmetric, or contain negative entries.
87    pub fn new(edge_weights: Vec<Vec<i32>>, requirements: Vec<Vec<i32>>) -> Self {
88        let n = edge_weights.len();
89        assert!(n >= 2, "must have at least 2 vertices");
90        assert_eq!(
91            requirements.len(),
92            n,
93            "requirements matrix must have same size as edge_weights"
94        );
95
96        for (i, row) in edge_weights.iter().enumerate() {
97            assert_eq!(
98                row.len(),
99                n,
100                "edge_weights must be square: row {i} has length {} but expected {n}",
101                row.len()
102            );
103            assert_eq!(
104                row[i], 0,
105                "diagonal of edge_weights must be zero: edge_weights[{i}][{i}] = {}",
106                row[i]
107            );
108        }
109
110        for (i, row) in requirements.iter().enumerate() {
111            assert_eq!(
112                row.len(),
113                n,
114                "requirements must be square: row {i} has length {} but expected {n}",
115                row.len()
116            );
117            assert_eq!(
118                row[i], 0,
119                "diagonal of requirements must be zero: requirements[{i}][{i}] = {}",
120                row[i]
121            );
122        }
123
124        // Check symmetry and non-negativity
125        for i in 0..n {
126            for j in (i + 1)..n {
127                assert_eq!(
128                    edge_weights[i][j], edge_weights[j][i],
129                    "edge_weights must be symmetric: w[{i}][{j}]={} != w[{j}][{i}]={}",
130                    edge_weights[i][j], edge_weights[j][i]
131                );
132                assert!(
133                    edge_weights[i][j] >= 0,
134                    "edge_weights must be non-negative: w[{i}][{j}]={}",
135                    edge_weights[i][j]
136                );
137                assert_eq!(
138                    requirements[i][j], requirements[j][i],
139                    "requirements must be symmetric: r[{i}][{j}]={} != r[{j}][{i}]={}",
140                    requirements[i][j], requirements[j][i]
141                );
142                assert!(
143                    requirements[i][j] >= 0,
144                    "requirements must be non-negative: r[{i}][{j}]={}",
145                    requirements[i][j]
146                );
147            }
148        }
149
150        Self {
151            num_vertices: n,
152            edge_weights,
153            requirements,
154        }
155    }
156
157    /// Returns the number of vertices.
158    pub fn num_vertices(&self) -> usize {
159        self.num_vertices
160    }
161
162    /// Returns the number of edges in the complete graph K_n.
163    pub fn num_edges(&self) -> usize {
164        self.num_vertices * (self.num_vertices - 1) / 2
165    }
166
167    /// Returns the edge weight matrix.
168    pub fn edge_weights(&self) -> &Vec<Vec<i32>> {
169        &self.edge_weights
170    }
171
172    /// Returns the requirements matrix.
173    pub fn requirements(&self) -> &Vec<Vec<i32>> {
174        &self.requirements
175    }
176
177    /// Returns the list of edges in lexicographic order: (0,1), (0,2), ..., (n-2,n-1).
178    pub fn edges(&self) -> Vec<(usize, usize)> {
179        let n = self.num_vertices;
180        let mut edges = Vec::with_capacity(self.num_edges());
181        for i in 0..n {
182            for j in (i + 1)..n {
183                edges.push((i, j));
184            }
185        }
186        edges
187    }
188
189    /// Map a pair (i, j) with i < j to its edge index.
190    pub fn edge_index(i: usize, j: usize, n: usize) -> usize {
191        debug_assert!(i < j && j < n);
192        i * n - i * (i + 1) / 2 + (j - i - 1)
193    }
194}
195
196/// Check if a configuration forms a valid spanning tree of K_n.
197fn is_valid_spanning_tree(n: usize, edges: &[(usize, usize)], config: &[usize]) -> bool {
198    if config.len() != edges.len() {
199        return false;
200    }
201
202    // Check all values are 0 or 1
203    if config.iter().any(|&v| v > 1) {
204        return false;
205    }
206
207    // Count selected edges: must be exactly n-1
208    let selected_count: usize = config.iter().sum();
209    if selected_count != n - 1 {
210        return false;
211    }
212
213    // Build adjacency and check connectivity via BFS
214    let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
215    for (idx, &sel) in config.iter().enumerate() {
216        if sel == 1 {
217            let (u, v) = edges[idx];
218            adj[u].push(v);
219            adj[v].push(u);
220        }
221    }
222
223    let mut visited = vec![false; n];
224    let mut queue = VecDeque::new();
225    visited[0] = true;
226    queue.push_back(0);
227    while let Some(v) = queue.pop_front() {
228        for &u in &adj[v] {
229            if !visited[u] {
230                visited[u] = true;
231                queue.push_back(u);
232            }
233        }
234    }
235
236    visited.iter().all(|&v| v)
237}
238
239/// Compute the communication cost of a spanning tree.
240///
241/// For each pair (u, v) with u < v, compute W_T(u,v) via BFS in the tree,
242/// then accumulate r(u,v) * W_T(u,v).
243fn communication_cost(
244    n: usize,
245    edges: &[(usize, usize)],
246    config: &[usize],
247    edge_weights: &[Vec<i32>],
248    requirements: &[Vec<i32>],
249) -> i64 {
250    // Build weighted adjacency list for the tree
251    let mut adj: Vec<Vec<(usize, i32)>> = vec![vec![]; n];
252    for (idx, &sel) in config.iter().enumerate() {
253        if sel == 1 {
254            let (u, v) = edges[idx];
255            let w = edge_weights[u][v];
256            adj[u].push((v, w));
257            adj[v].push((u, w));
258        }
259    }
260
261    let mut total_cost: i64 = 0;
262
263    // For each source vertex, BFS to find path weights to all other vertices
264    for src in 0..n {
265        let mut dist = vec![-1i64; n];
266        dist[src] = 0;
267        let mut queue = VecDeque::new();
268        queue.push_back(src);
269        while let Some(u) = queue.pop_front() {
270            for &(v, w) in &adj[u] {
271                if dist[v] < 0 {
272                    dist[v] = dist[u] + w as i64;
273                    queue.push_back(v);
274                }
275            }
276        }
277
278        // Accumulate r(src, dst) * W_T(src, dst) for dst > src
279        for (dst, &d) in dist.iter().enumerate().skip(src + 1) {
280            total_cost += requirements[src][dst] as i64 * d;
281        }
282    }
283
284    total_cost
285}
286
287impl Problem for OptimumCommunicationSpanningTree {
288    const NAME: &'static str = "OptimumCommunicationSpanningTree";
289    type Value = Min<i64>;
290
291    fn variant() -> Vec<(&'static str, &'static str)> {
292        crate::variant_params![]
293    }
294
295    fn dims(&self) -> Vec<usize> {
296        vec![2; self.num_edges()]
297    }
298
299    fn evaluate(&self, config: &[usize]) -> Min<i64> {
300        let edges = self.edges();
301        if !is_valid_spanning_tree(self.num_vertices, &edges, config) {
302            return Min(None);
303        }
304        Min(Some(communication_cost(
305            self.num_vertices,
306            &edges,
307            config,
308            &self.edge_weights,
309            &self.requirements,
310        )))
311    }
312}
313
314crate::declare_variants! {
315    default OptimumCommunicationSpanningTree => "2^num_edges",
316}
317
318#[cfg(feature = "example-db")]
319pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
320    // K4 example from issue #906
321    // Edge weights:
322    //   w(0,1)=1, w(0,2)=3, w(0,3)=2, w(1,2)=2, w(1,3)=4, w(2,3)=1
323    // Requirements:
324    //   r(0,1)=2, r(0,2)=1, r(0,3)=3, r(1,2)=1, r(1,3)=1, r(2,3)=2
325    // Optimal tree: {(0,1), (0,3), (2,3)} = edges at indices 0, 2, 5
326    // Optimal cost: 20
327    let edge_weights = vec![
328        vec![0, 1, 3, 2],
329        vec![1, 0, 2, 4],
330        vec![3, 2, 0, 1],
331        vec![2, 4, 1, 0],
332    ];
333    let requirements = vec![
334        vec![0, 2, 1, 3],
335        vec![2, 0, 1, 1],
336        vec![1, 1, 0, 2],
337        vec![3, 1, 2, 0],
338    ];
339    // Edges in lex order: (0,1)=idx0, (0,2)=idx1, (0,3)=idx2, (1,2)=idx3, (1,3)=idx4, (2,3)=idx5
340    // Optimal tree: {(0,1), (0,3), (2,3)} -> config = [1, 0, 1, 0, 0, 1]
341    vec![crate::example_db::specs::ModelExampleSpec {
342        id: "optimum_communication_spanning_tree",
343        instance: Box::new(OptimumCommunicationSpanningTree::new(
344            edge_weights,
345            requirements,
346        )),
347        optimal_config: vec![1, 0, 1, 0, 0, 1],
348        optimal_value: serde_json::json!(20),
349    }]
350}
351
352#[cfg(test)]
353#[path = "../../unit_tests/models/misc/optimum_communication_spanning_tree.rs"]
354mod tests;