problemreductions/models/graph/
steiner_tree_in_graphs.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Min, One, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "SteinerTreeInGraphs",
16 display_name: "Steiner Tree in Graphs",
17 aliases: &[],
18 dimensions: &[
19 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20 VariantDimension::new("weight", "i64", &["One", "i64"]),
21 ],
22 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Find minimum weight subtree connecting all terminal vertices",
25 fields: SteinerTreeInGraphsCreateSpec::<i64>::FIELDS,
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct SteinerTreeInGraphs<G, W> {
69 graph: G,
71 terminals: Vec<usize>,
73 edge_weights: Vec<W>,
75}
76
77#[derive(Debug, Deserialize, crate::CreateSpec)]
78struct SteinerTreeInGraphsCreateSpec<W> {
79 graph: SimpleGraph,
81 terminals: Vec<usize>,
83 edge_weights: Option<Vec<W>>,
85}
86impl<W> TryFrom<SteinerTreeInGraphsCreateSpec<W>> for SteinerTreeInGraphs<SimpleGraph, W>
87where
88 W: WeightElement,
89{
90 type Error = crate::registry::ConstructionError;
91 fn try_from(spec: SteinerTreeInGraphsCreateSpec<W>) -> Result<Self, Self::Error> {
92 let count = spec.graph.num_edges();
93 let edge_weights = spec
94 .edge_weights
95 .unwrap_or_else(|| (0..count).map(|_| W::unit()).collect());
96 if edge_weights.len() != count {
97 return Err(format!(
98 "edge_weights has {} entries, expected {count}",
99 edge_weights.len()
100 )
101 .into());
102 }
103 if let Some(&terminal) = spec
104 .terminals
105 .iter()
106 .find(|&&t| t >= spec.graph.num_vertices())
107 {
108 return Err(format!("terminal {terminal} is outside the graph").into());
109 }
110 Ok(Self::new(spec.graph, spec.terminals, edge_weights))
111 }
112}
113
114impl<G: Graph, W: Clone + Default> SteinerTreeInGraphs<G, W> {
115 pub fn new(graph: G, terminals: Vec<usize>, edge_weights: Vec<W>) -> Self {
120 assert_eq!(
121 edge_weights.len(),
122 graph.num_edges(),
123 "edge_weights length must match num_edges"
124 );
125 for &t in &terminals {
126 assert!(
127 t < graph.num_vertices(),
128 "terminal vertex {} out of bounds (num_vertices = {})",
129 t,
130 graph.num_vertices()
131 );
132 }
133 Self {
134 graph,
135 terminals,
136 edge_weights,
137 }
138 }
139
140 pub fn graph(&self) -> &G {
142 &self.graph
143 }
144
145 pub fn terminals(&self) -> &[usize] {
147 &self.terminals
148 }
149
150 pub fn edges(&self) -> Vec<(usize, usize, W)> {
152 self.graph
153 .edges()
154 .into_iter()
155 .zip(self.edge_weights.iter().cloned())
156 .map(|((u, v), w)| (u, v, w))
157 .collect()
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 is_weighted(&self) -> bool
173 where
174 W: WeightElement,
175 {
176 !W::IS_UNIT
177 }
178
179 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
181 if config.len() != self.graph.num_edges() {
182 return false;
183 }
184 let selected: Vec<bool> = config.iter().map(|&s| s == 1).collect();
185 is_steiner_tree(&self.graph, &self.terminals, &selected)
186 }
187}
188
189impl<G: Graph, W: WeightElement> SteinerTreeInGraphs<G, W> {
190 pub fn num_vertices(&self) -> usize {
192 self.graph().num_vertices()
193 }
194
195 pub fn num_edges(&self) -> usize {
197 self.graph().num_edges()
198 }
199
200 pub fn num_terminals(&self) -> usize {
202 self.terminals.len()
203 }
204}
205
206impl<G, W> Problem for SteinerTreeInGraphs<G, W>
207where
208 G: Graph + crate::variant::VariantParam,
209 W: WeightElement + crate::variant::VariantParam,
210{
211 const NAME: &'static str = "SteinerTreeInGraphs";
212 type Solution = Vec<bool>;
213 type Value = Min<W::Sum>;
214
215 crate::problem_parameters![
216 ("num_edges", num_edges),
217 ("num_terminals", num_terminals),
218 ("num_vertices", num_vertices),
219 ];
220
221 fn variant() -> Vec<(&'static str, &'static str)> {
222 crate::variant_params![G, W]
223 }
224
225 fn evaluate(
226 &self,
227 config: &Self::Solution,
228 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
229 Ok({
230 if config.len() != self.graph.num_edges() {
231 return Err(crate::traits::EvaluationError::InvalidConfiguration(
232 "edge-selection length does not match the graph".into(),
233 ));
234 }
235 let selected = config;
236 if !is_steiner_tree(&self.graph, &self.terminals, selected) {
237 return Ok(Min(None));
238 }
239 let mut total = W::Sum::zero();
240 for (idx, &sel) in config.iter().enumerate() {
241 if sel {
242 if let Some(w) = self.edge_weights.get(idx) {
243 total = W::checked_add_to_sum(
244 total,
245 w.to_sum(),
246 "summing Steiner tree edge weights",
247 )?;
248 }
249 }
250 }
251 Min(Some(total))
252 })
253 }
254}
255
256impl<G, W> crate::solvers::BruteForceProblem for SteinerTreeInGraphs<G, W>
257where
258 G: Graph + crate::variant::VariantParam,
259 W: WeightElement + crate::variant::VariantParam,
260{
261 fn dimensions(&self) -> Vec<usize> {
262 vec![2; self.graph.num_edges()]
263 }
264}
265
266pub(crate) fn is_steiner_tree<G: Graph>(graph: &G, terminals: &[usize], selected: &[bool]) -> bool {
278 assert_eq!(
279 selected.len(),
280 graph.num_edges(),
281 "selected length must match num_edges"
282 );
283
284 if terminals.is_empty() {
286 return true;
287 }
288
289 if terminals.len() == 1 {
292 return true;
293 }
294
295 let n = graph.num_vertices();
297 let edges = graph.edges();
298 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
299
300 let mut has_any_edge = false;
301 for (idx, &sel) in selected.iter().enumerate() {
302 if sel {
303 let (u, v) = edges[idx];
304 adj[u].push(v);
305 adj[v].push(u);
306 has_any_edge = true;
307 }
308 }
309
310 if !has_any_edge {
311 return false;
312 }
313
314 let start = terminals[0];
316 let mut visited = vec![false; n];
317 let mut queue = std::collections::VecDeque::new();
318 visited[start] = true;
319 queue.push_back(start);
320
321 while let Some(node) = queue.pop_front() {
322 for &neighbor in &adj[node] {
323 if !visited[neighbor] {
324 visited[neighbor] = true;
325 queue.push_back(neighbor);
326 }
327 }
328 }
329
330 terminals.iter().all(|&t| visited[t])
332}
333
334crate::impl_random_generate!(SteinerTreeInGraphs<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
335 if spec.num_vertices < 2 {
336 return Err("num_vertices must be at least 2".to_string().into());
337 }
338 let graph = spec.graph()?;
339 let terminals = (0..std::cmp::max(2, spec.num_vertices / 2)).collect();
340 let weights = vec![1; graph.num_edges()];
341 Ok(SteinerTreeInGraphs::new(graph, terminals, weights))
342});
343
344#[derive(Debug, Deserialize, crate::CreateSpec)]
345struct SteinerTreeInGraphsOneCreateSpec {
346 graph: SimpleGraph,
348 terminals: Vec<usize>,
349}
350
351impl TryFrom<SteinerTreeInGraphsOneCreateSpec> for SteinerTreeInGraphs<SimpleGraph, One> {
352 type Error = crate::registry::ConstructionError;
353 fn try_from(spec: SteinerTreeInGraphsOneCreateSpec) -> Result<Self, Self::Error> {
354 let weights = vec![One; spec.graph.num_edges()];
355 if let Some(&terminal) = spec
356 .terminals
357 .iter()
358 .find(|&&t| t >= spec.graph.num_vertices())
359 {
360 return Err(format!("terminal {terminal} is outside the graph").into());
361 }
362 Ok(Self::new(spec.graph, spec.terminals, weights))
363 }
364}
365
366crate::declare_variants! {
367 default SteinerTreeInGraphs<SimpleGraph, i64> => "2^num_terminals * num_vertices^3" create SteinerTreeInGraphsCreateSpec<i64> random,
368 SteinerTreeInGraphs<SimpleGraph, One> => "2^num_terminals * num_vertices^3" create SteinerTreeInGraphsOneCreateSpec,
369}
370
371crate::register_brute_force! {
372 SteinerTreeInGraphs<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
373 SteinerTreeInGraphs<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
374}
375
376#[cfg(feature = "example-db")]
377pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
378 vec![crate::example_db::specs::ModelExampleSpec {
379 id: "steiner_tree_in_graphs_simplegraph",
380 instance: Box::new(SteinerTreeInGraphs::new(
381 SimpleGraph::new(
382 6,
383 vec![(0, 1), (0, 2), (1, 3), (2, 3), (2, 5), (3, 4), (4, 5)],
384 ),
385 vec![0, 3, 5],
386 vec![3, 2, 4, 1, 2, 3, 1],
387 )),
388 optimal_config: serde_json::json!(vec![false, true, false, true, true, false, false]),
390 optimal_value: serde_json::json!(5),
391 }]
392}
393
394#[cfg(test)]
395#[path = "../../unit_tests/models/graph/steiner_tree_in_graphs.rs"]
396mod tests;