1use crate::registry::{ConstructionError, CreateSpec, ProblemSchemaEntry, VariantDimension};
28use crate::topology::{Graph, SimpleGraph};
29use crate::traits::Problem;
30use crate::types::{Min, WeightElement};
31use crate::variant::VariantParam;
32use num_traits::Zero;
33use serde::{Deserialize, Serialize};
34use std::collections::VecDeque;
35
36inventory::submit! {
37 ProblemSchemaEntry {
38 name: "PrizeCollectingSteinerForest",
39 display_name: "Prize-Collecting Steiner Forest",
40 aliases: &[],
41 dimensions: &[
42 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
43 VariantDimension::new("weight", "i64", &["i64", "f64"]),
44 ],
45 category: crate::registry::ProblemCategory::Graph,
46 module_path: module_path!(),
47 description: "Find a forest minimizing omitted-prize plus edge-cost plus omega times the number of tree components",
48 fields: PrizeCollectingSteinerForestI64CreateSpec::FIELDS,
49 }
50}
51
52#[derive(Debug, Clone, Serialize)]
88pub struct PrizeCollectingSteinerForest<G, W> {
89 graph: G,
91 vertex_prizes: Vec<W>,
93 edge_costs: Vec<W>,
95 beta: W,
97 omega: W,
99}
100
101#[derive(Deserialize)]
102struct PrizeCollectingSteinerForestData<G, W> {
103 graph: G,
104 vertex_prizes: Vec<W>,
105 edge_costs: Vec<W>,
106 beta: W,
107 omega: W,
108}
109
110impl<'de, G, W> Deserialize<'de> for PrizeCollectingSteinerForest<G, W>
111where
112 G: Graph + Deserialize<'de>,
113 W: WeightElement + Deserialize<'de>,
114{
115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
116 where
117 D: serde::Deserializer<'de>,
118 {
119 let data = PrizeCollectingSteinerForestData::deserialize(deserializer)?;
120 Self::new(
121 data.graph,
122 data.vertex_prizes,
123 data.edge_costs,
124 data.beta,
125 data.omega,
126 )
127 .map_err(serde::de::Error::custom)
128 }
129}
130
131macro_rules! prize_collecting_steiner_forest_create_spec {
132 ($name:ident, $weight:ty, $one:expr) => {
133 #[derive(Debug, Deserialize, crate::CreateSpec)]
134 struct $name {
135 #[create(codec = "edge-list")]
136 graph: Vec<(usize, usize)>,
137 num_vertices: Option<usize>,
138 #[create(codec = "comma-separated")]
139 vertex_prizes: Option<Vec<$weight>>,
140 #[create(codec = "comma-separated")]
141 edge_costs: Option<Vec<$weight>>,
142 beta: $weight,
143 omega: $weight,
144 }
145
146 impl TryFrom<$name> for PrizeCollectingSteinerForest<SimpleGraph, $weight> {
147 type Error = ConstructionError;
148
149 fn try_from(spec: $name) -> Result<Self, Self::Error> {
150 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
151 let vertex_prizes = spec
152 .vertex_prizes
153 .unwrap_or_else(|| vec![$one; graph.num_vertices()]);
154 let edge_costs = spec
155 .edge_costs
156 .unwrap_or_else(|| vec![$one; graph.num_edges()]);
157 Self::new(graph, vertex_prizes, edge_costs, spec.beta, spec.omega)
158 }
159 }
160 };
161}
162
163prize_collecting_steiner_forest_create_spec!(PrizeCollectingSteinerForestI64CreateSpec, i64, 1);
164prize_collecting_steiner_forest_create_spec!(PrizeCollectingSteinerForestF64CreateSpec, f64, 1.0);
165
166fn simple_graph_from_create(
167 edges: Vec<(usize, usize)>,
168 num_vertices: Option<usize>,
169) -> Result<SimpleGraph, ConstructionError> {
170 if edges.is_empty() && num_vertices.is_none() {
171 return Err(ConstructionError::Conversion(
172 "num_vertices is required for an empty graph".into(),
173 ));
174 }
175 for (index, &(u, v)) in edges.iter().enumerate() {
176 if u == v {
177 return Err(ConstructionError::Conversion(format!(
178 "graph edge {index} is a self-loop at vertex {u}"
179 )));
180 }
181 }
182 let inferred = edges
183 .iter()
184 .flat_map(|&(u, v)| [u, v])
185 .max()
186 .map(|vertex| {
187 vertex.checked_add(1).ok_or_else(|| {
188 ConstructionError::IntegerOverflow(
189 "inferring the PrizeCollectingSteinerForest vertex count".into(),
190 )
191 })
192 })
193 .transpose()?
194 .unwrap_or(0);
195 let num_vertices = num_vertices.unwrap_or(inferred);
196 if num_vertices < inferred {
197 return Err(ConstructionError::Conversion(format!(
198 "num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}"
199 )));
200 }
201 Ok(SimpleGraph::new(num_vertices, edges))
202}
203
204impl<G: Graph, W: WeightElement> PrizeCollectingSteinerForest<G, W> {
205 pub fn new(
208 graph: G,
209 vertex_prizes: Vec<W>,
210 edge_costs: Vec<W>,
211 beta: W,
212 omega: W,
213 ) -> Result<Self, ConstructionError> {
214 if vertex_prizes.len() != graph.num_vertices() {
215 return Err(crate::registry::ConstructionError::length_mismatch(
216 "vertex_prizes",
217 vertex_prizes.len(),
218 graph.num_vertices(),
219 ));
220 }
221 if edge_costs.len() != graph.num_edges() {
222 return Err(crate::registry::ConstructionError::length_mismatch(
223 "edge_costs",
224 edge_costs.len(),
225 graph.num_edges(),
226 ));
227 }
228 for (index, prize) in vertex_prizes.iter().enumerate() {
229 prize.validate_element(&format!("vertex prize at index {index}"))?;
230 }
231 for (index, cost) in edge_costs.iter().enumerate() {
232 cost.validate_element(&format!("edge cost at index {index}"))?;
233 }
234 beta.validate_element("beta")?;
235 omega.validate_element("omega")?;
236 if vertex_prizes
237 .iter()
238 .chain(&edge_costs)
239 .chain([&beta, &omega])
240 .any(|value| value.to_sum() < W::Sum::zero())
241 {
242 return Err(ConstructionError::InvalidInput(
243 "vertex prizes, edge costs, beta, and omega must be nonnegative".into(),
244 ));
245 }
246 Ok(Self {
247 graph,
248 vertex_prizes,
249 edge_costs,
250 beta,
251 omega,
252 })
253 }
254
255 pub fn graph(&self) -> &G {
257 &self.graph
258 }
259
260 pub fn vertex_prizes(&self) -> &[W] {
262 &self.vertex_prizes
263 }
264
265 pub fn edge_costs(&self) -> &[W] {
267 &self.edge_costs
268 }
269
270 pub fn beta(&self) -> &W {
272 &self.beta
273 }
274
275 pub fn omega(&self) -> &W {
277 &self.omega
278 }
279}
280
281impl<G: Graph, W: WeightElement> PrizeCollectingSteinerForest<G, W> {
282 pub fn num_vertices(&self) -> usize {
284 self.graph.num_vertices()
285 }
286
287 pub fn num_edges(&self) -> usize {
289 self.graph.num_edges()
290 }
291
292 pub fn num_vertices_with_prize(&self) -> usize {
295 let zero = <W::Sum as Zero>::zero();
296 self.vertex_prizes
297 .iter()
298 .filter(|prize| prize.to_sum() > zero)
299 .count()
300 }
301
302 pub fn is_valid_solution(&self, solution: &(Vec<bool>, Vec<bool>)) -> bool {
305 forest_components(&self.graph, &solution.0, &solution.1).is_some()
306 }
307}
308
309impl<G, W> Problem for PrizeCollectingSteinerForest<G, W>
310where
311 G: Graph + VariantParam,
312 W: WeightElement + VariantParam,
313{
314 const NAME: &'static str = "PrizeCollectingSteinerForest";
315 type Solution = (Vec<bool>, Vec<bool>);
316 type Value = Min<W::Sum>;
317
318 crate::problem_parameters![
319 ("num_edges", num_edges),
320 ("num_vertices", num_vertices),
321 ("num_vertices_with_prize", num_vertices_with_prize),
322 ];
323
324 fn variant() -> Vec<(&'static str, &'static str)> {
325 crate::variant_params![G, W]
326 }
327
328 fn evaluate(
329 &self,
330 solution: &Self::Solution,
331 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
332 let (vertices, edges) = solution;
333 if vertices.len() != self.graph.num_vertices() || edges.len() != self.graph.num_edges() {
334 return Err(crate::traits::EvaluationError::InvalidConfiguration(
335 "Steiner forest selection dimensions do not match the graph".into(),
336 ));
337 }
338 Ok({
339 let kappa = match forest_components(&self.graph, vertices, edges) {
340 Some(kappa) => kappa,
341 None => return Ok(Min(None)),
342 };
343
344 let mut omitted_prizes = W::Sum::zero();
349 for (v, prize) in self.vertex_prizes.iter().enumerate() {
350 if !vertices[v] {
351 omitted_prizes = W::checked_add_to_sum(
352 omitted_prizes,
353 prize.to_sum(),
354 "summing omitted Steiner forest prizes",
355 )?;
356 }
357 }
358 let omitted_term = W::checked_mul_sum(
359 self.beta.to_sum(),
360 omitted_prizes,
361 "multiplying omitted prizes by beta",
362 )?;
363
364 let mut edge_term = W::Sum::zero();
365 for (i, cost) in self.edge_costs.iter().enumerate() {
366 if edges[i] {
367 edge_term = W::checked_add_to_sum(
368 edge_term,
369 cost.to_sum(),
370 "summing Steiner forest edge costs",
371 )?;
372 }
373 }
374
375 let omega_sum = self.omega.to_sum();
377 let mut kappa_sum = W::Sum::zero();
378 for _ in 0..kappa {
379 kappa_sum = W::checked_add_to_sum(
380 kappa_sum,
381 omega_sum.clone(),
382 "multiplying Steiner forest component penalty",
383 )?;
384 }
385
386 let mut total = W::Sum::zero();
387 total = W::checked_add_to_sum(
388 total,
389 omitted_term,
390 "summing Steiner forest objective terms",
391 )?;
392 total =
393 W::checked_add_to_sum(total, edge_term, "summing Steiner forest objective terms")?;
394 total =
395 W::checked_add_to_sum(total, kappa_sum, "summing Steiner forest objective terms")?;
396 Min(Some(total))
397 })
398 }
399}
400
401impl<G, W> crate::solvers::BruteForceProblem for PrizeCollectingSteinerForest<G, W>
402where
403 G: Graph + VariantParam,
404 W: WeightElement + VariantParam,
405{
406 fn dimensions(&self) -> Vec<usize> {
407 vec![2; self.graph.num_vertices() + self.graph.num_edges()]
408 }
409}
410
411fn forest_components<G: Graph>(
416 graph: &G,
417 selected_vertices: &[bool],
418 selected_edges: &[bool],
419) -> Option<usize> {
420 let n = graph.num_vertices();
421 let m = graph.num_edges();
422 if selected_vertices.len() != n || selected_edges.len() != m {
423 return None;
424 }
425 let edges = graph.edges();
426 let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
427 for (i, &(u, v)) in edges.iter().enumerate() {
428 if !selected_edges[i] {
429 continue;
430 }
431 if !selected_vertices[u] || !selected_vertices[v] {
432 return None;
433 }
434 adj[u].push((v, i));
435 adj[v].push((u, i));
436 }
437 let mut visited = vec![false; n];
438 let mut kappa: usize = 0;
439 for start in 0..n {
440 if !selected_vertices[start] || visited[start] {
441 continue;
442 }
443 kappa += 1;
444 visited[start] = true;
445 let mut parent_edge: Vec<Option<usize>> = vec![None; n];
446 let mut queue: VecDeque<usize> = VecDeque::new();
447 queue.push_back(start);
448 while let Some(u) = queue.pop_front() {
449 for &(w, edge_idx) in &adj[u] {
450 if parent_edge[u] == Some(edge_idx) {
451 continue;
452 }
453 if visited[w] {
454 return None; }
456 visited[w] = true;
457 parent_edge[w] = Some(edge_idx);
458 queue.push_back(w);
459 }
460 }
461 }
462 Some(kappa)
463}
464
465crate::declare_variants! {
466 default PrizeCollectingSteinerForest<SimpleGraph, i64> => "2^(num_vertices + num_edges)" create PrizeCollectingSteinerForestI64CreateSpec,
467 PrizeCollectingSteinerForest<SimpleGraph, f64> => "2^(num_vertices + num_edges)" create PrizeCollectingSteinerForestF64CreateSpec,
468}
469
470crate::register_brute_force! {
471 PrizeCollectingSteinerForest<SimpleGraph, i64> decode |problem: &PrizeCollectingSteinerForest<SimpleGraph, i64>, indices: Vec<usize>| { let split = problem.num_vertices(); (crate::config::config_to_bits(&indices[..split]), crate::config::config_to_bits(&indices[split..])) },
472 PrizeCollectingSteinerForest<SimpleGraph, f64> decode |problem: &PrizeCollectingSteinerForest<SimpleGraph, f64>, indices: Vec<usize>| { let split = problem.num_vertices(); (crate::config::config_to_bits(&indices[..split]), crate::config::config_to_bits(&indices[split..])) },
473}
474
475#[cfg(feature = "example-db")]
476pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
477 vec![crate::example_db::specs::ModelExampleSpec {
482 id: "prize_collecting_steiner_forest_simplegraph",
483 instance: Box::new(
484 PrizeCollectingSteinerForest::<SimpleGraph, i64>::new(
485 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
486 vec![5, 2, 5],
487 vec![1, 6],
488 1,
489 2,
490 )
491 .unwrap(),
492 ),
493 optimal_config: serde_json::json!((vec![true, true, true], vec![true, false])),
494 optimal_value: serde_json::json!(5),
495 }]
496}
497
498#[cfg(test)]
499#[path = "../../unit_tests/models/graph/prize_collecting_steiner_forest.rs"]
500mod tests;