1use std::collections::BTreeSet;
7
8use num_traits::Zero;
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 registry::{CreateSpec, ProblemSchemaEntry, VariantDimension},
13 topology::{Graph, SimpleGraph},
14 traits::Problem,
15 types::{Min, One, WeightElement},
16};
17
18inventory::submit! {
19 ProblemSchemaEntry {
20 name: "SteinerTree",
21 display_name: "Steiner Tree",
22 aliases: &[],
23 dimensions: &[
24 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
25 VariantDimension::new("weight", "i64", &["One", "i64"]),
26 ],
27 category: crate::registry::ProblemCategory::Graph,
28 module_path: module_path!(),
29 description: "Find minimum weight tree connecting terminal vertices",
30 fields: SteinerTreeCreateSpec::<i64>::FIELDS,
31 }
32}
33
34#[derive(Debug, Clone, Serialize)]
57pub struct SteinerTree<G, W> {
58 graph: G,
60 edge_weights: Vec<W>,
62 terminals: Vec<usize>,
64}
65
66impl<'de, G, W> Deserialize<'de> for SteinerTree<G, W>
67where
68 G: Graph + Deserialize<'de>,
69 W: Clone + Default + Deserialize<'de>,
70{
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: serde::Deserializer<'de>,
74 {
75 #[derive(Deserialize)]
76 struct Fields<G, W> {
77 graph: G,
78 edge_weights: Vec<W>,
79 terminals: Vec<usize>,
80 }
81
82 let fields = Fields::deserialize(deserializer)?;
83 Self::try_new(fields.graph, fields.edge_weights, fields.terminals)
84 .map_err(serde::de::Error::custom)
85 }
86}
87
88#[derive(Debug, Deserialize, crate::CreateSpec)]
89struct SteinerTreeCreateSpec<W> {
90 graph: SimpleGraph,
92 edge_weights: Vec<W>,
94 terminals: Vec<usize>,
96}
97
98impl<W: Clone + Default> TryFrom<SteinerTreeCreateSpec<W>> for SteinerTree<SimpleGraph, W> {
99 type Error = crate::registry::ConstructionError;
100 fn try_from(spec: SteinerTreeCreateSpec<W>) -> Result<Self, Self::Error> {
101 Self::try_new(spec.graph, spec.edge_weights, spec.terminals)
102 }
103}
104
105impl<G: Graph, W: Clone + Default> SteinerTree<G, W> {
106 fn try_new(
107 graph: G,
108 edge_weights: Vec<W>,
109 terminals: Vec<usize>,
110 ) -> Result<Self, crate::registry::ConstructionError> {
111 if edge_weights.len() != graph.num_edges() {
112 return Err(crate::registry::ConstructionError::length_mismatch(
113 "edge_weights",
114 edge_weights.len(),
115 graph.num_edges(),
116 ));
117 }
118 if terminals.is_empty() {
119 return Err("at least one terminal required".into());
120 }
121 let distinct_terminals: BTreeSet<_> = terminals.iter().copied().collect();
122 if distinct_terminals.len() != terminals.len() {
123 return Err("terminals must be distinct".into());
124 }
125 let n = graph.num_vertices();
126 if let Some(&terminal) = terminals.iter().find(|&&terminal| terminal >= n) {
127 return Err(format!("terminal {terminal} out of range (num_vertices = {n})").into());
128 }
129 Ok(Self {
130 graph,
131 edge_weights,
132 terminals,
133 })
134 }
135
136 pub fn new(graph: G, edge_weights: Vec<W>, terminals: Vec<usize>) -> Self {
138 Self::try_new(graph, edge_weights, terminals).unwrap_or_else(|error| panic!("{error}"))
139 }
140
141 pub fn unit_weights(graph: G, terminals: Vec<usize>) -> Self
143 where
144 W: WeightElement,
145 {
146 let edge_weights = vec![W::unit(); graph.num_edges()];
147 Self::new(graph, edge_weights, terminals)
148 }
149
150 pub fn graph(&self) -> &G {
152 &self.graph
153 }
154
155 pub fn edge_weights(&self) -> &[W] {
157 &self.edge_weights
158 }
159
160 pub fn set_weights(&mut self, weights: Vec<W>) {
162 assert_eq!(weights.len(), self.graph.num_edges());
163 self.edge_weights = weights;
164 }
165
166 pub fn weights(&self) -> Vec<W> {
168 self.edge_weights.clone()
169 }
170
171 pub fn terminals(&self) -> &[usize] {
173 &self.terminals
174 }
175
176 pub fn is_weighted(&self) -> bool
178 where
179 W: WeightElement,
180 {
181 !W::IS_UNIT
182 }
183
184 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
186 is_valid_steiner_tree(&self.graph, &self.terminals, config)
187 }
188}
189
190impl<G: Graph, W: WeightElement> SteinerTree<G, W> {
191 pub fn num_vertices(&self) -> usize {
193 self.graph.num_vertices()
194 }
195
196 pub fn num_edges(&self) -> usize {
198 self.graph.num_edges()
199 }
200
201 pub fn num_terminals(&self) -> usize {
203 self.terminals.len()
204 }
205}
206
207fn is_valid_steiner_tree<G: Graph>(graph: &G, terminals: &[usize], config: &[bool]) -> bool {
211 let n = graph.num_vertices();
212 let edges = graph.edges();
213 if config.len() != edges.len() {
214 return false;
215 }
216
217 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
219 let mut selected_count = 0usize;
220 let mut involved = vec![false; n];
221 for (idx, &sel) in config.iter().enumerate() {
222 if sel {
223 let (u, v) = edges[idx];
224 adj[u].push(v);
225 adj[v].push(u);
226 involved[u] = true;
227 involved[v] = true;
228 selected_count += 1;
229 }
230 }
231
232 if selected_count == 0 {
233 return terminals.len() == 1;
234 }
235
236 let start = terminals[0];
238 let mut visited = vec![false; n];
239 let mut queue = std::collections::VecDeque::new();
240 visited[start] = true;
241 queue.push_back(start);
242 while let Some(v) = queue.pop_front() {
243 for &u in &adj[v] {
244 if !visited[u] {
245 visited[u] = true;
246 queue.push_back(u);
247 }
248 }
249 }
250
251 if !terminals.iter().all(|&t| visited[t]) {
253 return false;
254 }
255
256 if (0..n).any(|i| involved[i] && !visited[i]) {
258 return false;
259 }
260
261 let involved_count = involved.iter().filter(|&&x| x).count();
263 selected_count == involved_count - 1
264}
265
266impl<G, W> Problem for SteinerTree<G, W>
267where
268 G: Graph + crate::variant::VariantParam,
269 W: WeightElement + crate::variant::VariantParam,
270{
271 const NAME: &'static str = "SteinerTree";
272 type Solution = Vec<bool>;
273 type Value = Min<W::Sum>;
274
275 crate::problem_parameters![
276 ("num_edges", num_edges),
277 ("num_terminals", num_terminals),
278 ("num_vertices", num_vertices),
279 ];
280
281 fn variant() -> Vec<(&'static str, &'static str)> {
282 crate::variant_params![G, W]
283 }
284
285 fn evaluate(
286 &self,
287 config: &Self::Solution,
288 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
289 if config.len() != self.graph.num_edges() {
290 return Err(crate::traits::EvaluationError::InvalidConfiguration(
291 "edge-selection length does not match the graph".into(),
292 ));
293 }
294 Ok({
295 if !is_valid_steiner_tree(&self.graph, &self.terminals, config) {
296 return Ok(Min(None));
297 }
298 let mut total = W::Sum::zero();
299 for (idx, &selected) in config.iter().enumerate() {
300 if selected {
301 if let Some(w) = self.edge_weights.get(idx) {
302 total = W::checked_add_to_sum(
303 total,
304 w.to_sum(),
305 "summing Steiner tree edge weights",
306 )?;
307 }
308 }
309 }
310 Min(Some(total))
311 })
312 }
313}
314
315impl<G, W> crate::solvers::BruteForceProblem for SteinerTree<G, W>
316where
317 G: Graph + crate::variant::VariantParam,
318 W: WeightElement + crate::variant::VariantParam,
319{
320 fn dimensions(&self) -> Vec<usize> {
321 vec![2; self.graph.num_edges()]
322 }
323}
324
325crate::impl_random_generate!(SteinerTree<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
326 if spec.num_vertices < 2 {
327 return Err("num_vertices must be at least 2".to_string().into());
328 }
329 let mut state = crate::random::lcg_init(crate::random::seed_to_u64(spec.seed)?);
330 let graph = spec.graph()?;
331 for _ in 0..spec.num_vertices * spec.num_vertices {
332 crate::random::lcg_step(&mut state);
333 }
334 let weights = (0..graph.num_edges()).map(|_| (crate::random::lcg_step(&mut state) * 9.0) as i64 + 1).collect();
335 let count = std::cmp::max(2, spec.num_vertices * 2 / 5);
336 let terminals = crate::random::lcg_choose(&mut state, spec.num_vertices, count)
337 .map_err(|error| error.to_string())?;
338 Ok(SteinerTree::new(graph, weights, terminals))
339});
340
341#[derive(Debug, Deserialize, crate::CreateSpec)]
342struct SteinerTreeOneCreateSpec {
343 graph: SimpleGraph,
345 terminals: Vec<usize>,
346}
347
348impl TryFrom<SteinerTreeOneCreateSpec> for SteinerTree<SimpleGraph, One> {
349 type Error = crate::registry::ConstructionError;
350 fn try_from(spec: SteinerTreeOneCreateSpec) -> Result<Self, Self::Error> {
351 let weights = vec![One; spec.graph.num_edges()];
352 Self::try_new(spec.graph, weights, spec.terminals)
353 }
354}
355
356crate::declare_variants! {
357 default SteinerTree<SimpleGraph, i64> => "2^num_vertices * 0.5^num_terminals * num_vertices^2" create SteinerTreeCreateSpec<i64> random,
358 SteinerTree<SimpleGraph, One> => "3^num_terminals * num_vertices + 2^num_terminals * num_vertices^2" create SteinerTreeOneCreateSpec,
359}
360
361crate::register_brute_force! {
362 SteinerTree<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
363 SteinerTree<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
364}
365
366#[cfg(feature = "example-db")]
367pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
368 vec![crate::example_db::specs::ModelExampleSpec {
369 id: "steiner_tree_simplegraph",
370 instance: Box::new(SteinerTree::new(
371 SimpleGraph::new(
372 5,
373 vec![(0, 1), (0, 3), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4)],
374 ),
375 vec![2, 5, 2, 1, 5, 6, 1],
376 vec![0, 2, 4],
377 )),
378 optimal_config: serde_json::json!(vec![true, false, true, true, false, false, true]),
379 optimal_value: serde_json::json!(6),
380 }]
381}
382
383#[cfg(test)]
384#[path = "../../unit_tests/models/graph/steiner_tree.rs"]
385mod tests;