problemreductions/models/graph/
prize_collecting_steiner_forest.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry, 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", "i32", &["i32", "f64"]),
44 ],
45 module_path: module_path!(),
46 description: "Find a forest minimizing omitted-prize plus edge-cost plus omega times the number of tree components",
47 fields: &[
48 FieldInfo { name: "graph", type_name: "G", description: "The underlying network G=(V,E)" },
49 FieldInfo { name: "vertex_prizes", type_name: "Vec<W>", description: "Nonnegative vertex prizes p: V -> R_{>=0}" },
50 FieldInfo { name: "edge_costs", type_name: "Vec<W>", description: "Nonnegative edge costs c: E -> R_{>=0} in graph.edges() order" },
51 FieldInfo { name: "beta", type_name: "W", description: "Tradeoff coefficient beta >= 0 on the omitted-prize term" },
52 FieldInfo { name: "omega", type_name: "W", description: "Per-component penalty omega >= 0 on the number of tree components" },
53 ],
54 }
55}
56
57inventory::submit! {
58 ProblemSizeFieldEntry {
59 name: "PrizeCollectingSteinerForest",
60 fields: &["num_vertices", "num_edges", "num_vertices_with_prize"],
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
99#[serde(bound(deserialize = "G: serde::Deserialize<'de>, W: serde::Deserialize<'de>"))]
100pub struct PrizeCollectingSteinerForest<G, W> {
101 graph: G,
103 vertex_prizes: Vec<W>,
105 edge_costs: Vec<W>,
107 beta: W,
109 omega: W,
111}
112
113impl<G: Graph, W: Clone + Default> PrizeCollectingSteinerForest<G, W> {
114 pub fn new(graph: G, vertex_prizes: Vec<W>, edge_costs: Vec<W>, beta: W, omega: W) -> Self {
120 assert_eq!(
121 vertex_prizes.len(),
122 graph.num_vertices(),
123 "vertex_prizes length must match graph num_vertices"
124 );
125 assert_eq!(
126 edge_costs.len(),
127 graph.num_edges(),
128 "edge_costs length must match graph num_edges"
129 );
130 Self {
131 graph,
132 vertex_prizes,
133 edge_costs,
134 beta,
135 omega,
136 }
137 }
138
139 pub fn graph(&self) -> &G {
141 &self.graph
142 }
143
144 pub fn vertex_prizes(&self) -> &[W] {
146 &self.vertex_prizes
147 }
148
149 pub fn edge_costs(&self) -> &[W] {
151 &self.edge_costs
152 }
153
154 pub fn beta(&self) -> &W {
156 &self.beta
157 }
158
159 pub fn omega(&self) -> &W {
161 &self.omega
162 }
163}
164
165impl<G: Graph, W: WeightElement> PrizeCollectingSteinerForest<G, W> {
166 pub fn num_vertices(&self) -> usize {
168 self.graph.num_vertices()
169 }
170
171 pub fn num_edges(&self) -> usize {
173 self.graph.num_edges()
174 }
175
176 pub fn num_vertices_with_prize(&self) -> usize {
179 let zero = <W::Sum as Zero>::zero();
180 self.vertex_prizes
181 .iter()
182 .filter(|prize| prize.to_sum() > zero)
183 .count()
184 }
185
186 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
189 forest_components(&self.graph, config).is_some()
190 }
191}
192
193impl<G, W> Problem for PrizeCollectingSteinerForest<G, W>
194where
195 G: Graph + VariantParam,
196 W: WeightElement + VariantParam,
197{
198 const NAME: &'static str = "PrizeCollectingSteinerForest";
199 type Value = Min<W::Sum>;
200
201 fn variant() -> Vec<(&'static str, &'static str)> {
202 crate::variant_params![G, W]
203 }
204
205 fn dims(&self) -> Vec<usize> {
206 vec![2; self.graph.num_vertices() + self.graph.num_edges()]
207 }
208
209 fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
210 let n = self.graph.num_vertices();
211 let kappa = match forest_components(&self.graph, config) {
212 Some(kappa) => kappa,
213 None => return Min(None),
214 };
215
216 let mut omitted_prizes = W::Sum::zero();
223 for (v, prize) in self.vertex_prizes.iter().enumerate() {
224 if config[v] == 0 {
225 omitted_prizes += prize.to_sum();
226 }
227 }
228 let omitted_term = self.beta.to_sum() * omitted_prizes;
229
230 let mut edge_term = W::Sum::zero();
231 for (i, cost) in self.edge_costs.iter().enumerate() {
232 if config[n + i] == 1 {
233 edge_term += cost.to_sum();
234 }
235 }
236
237 let omega_sum = self.omega.to_sum();
241 let mut kappa_sum = W::Sum::zero();
242 for _ in 0..kappa {
243 kappa_sum += omega_sum.clone();
244 }
245
246 let mut total = W::Sum::zero();
247 total += omitted_term;
248 total += edge_term;
249 total += kappa_sum;
250 Min(Some(total))
251 }
252}
253
254fn forest_components<G: Graph>(graph: &G, config: &[usize]) -> Option<usize> {
259 let n = graph.num_vertices();
260 let m = graph.num_edges();
261 if config.len() != n + m {
262 return None;
263 }
264 let edges = graph.edges();
265 let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); n];
266 for (i, &(u, v)) in edges.iter().enumerate() {
267 let y_e = config[n + i];
268 if y_e == 0 {
269 continue;
270 }
271 if y_e != 1 {
272 return None;
273 }
274 if config[u] != 1 || config[v] != 1 {
275 return None;
276 }
277 adj[u].push((v, i));
278 adj[v].push((u, i));
279 }
280 let mut visited = vec![false; n];
281 let mut kappa: usize = 0;
282 for start in 0..n {
283 if config[start] != 1 || visited[start] {
284 continue;
285 }
286 kappa += 1;
287 visited[start] = true;
288 let mut parent_edge: Vec<Option<usize>> = vec![None; n];
289 let mut queue: VecDeque<usize> = VecDeque::new();
290 queue.push_back(start);
291 while let Some(u) = queue.pop_front() {
292 for &(w, edge_idx) in &adj[u] {
293 if parent_edge[u] == Some(edge_idx) {
294 continue;
295 }
296 if visited[w] {
297 return None; }
299 visited[w] = true;
300 parent_edge[w] = Some(edge_idx);
301 queue.push_back(w);
302 }
303 }
304 }
305 Some(kappa)
306}
307
308crate::declare_variants! {
309 default PrizeCollectingSteinerForest<SimpleGraph, i32> => "2^(num_vertices + num_edges)",
310 PrizeCollectingSteinerForest<SimpleGraph, f64> => "2^(num_vertices + num_edges)",
311}
312
313#[cfg(feature = "example-db")]
314pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
315 vec![crate::example_db::specs::ModelExampleSpec {
320 id: "prize_collecting_steiner_forest_simplegraph_i32",
321 instance: Box::new(PrizeCollectingSteinerForest::<SimpleGraph, i32>::new(
322 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
323 vec![5, 2, 5],
324 vec![1, 6],
325 1,
326 2,
327 )),
328 optimal_config: vec![1, 1, 1, 1, 0],
330 optimal_value: serde_json::json!(5),
331 }]
332}
333
334#[cfg(test)]
335#[path = "../../unit_tests/models/graph/prize_collecting_steiner_forest.rs"]
336mod tests;