problemreductions/rules/
prizecollectingsteinerforest_steinertree.rs1use crate::models::graph::{PrizeCollectingSteinerForest, SteinerTree};
37use crate::reduction;
38use crate::rules::traits::{ReduceTo, ReductionResult};
39use crate::topology::{Graph, SimpleGraph};
40
41#[derive(Debug, Clone)]
49pub struct ReductionPCSFToSteinerTree {
50 target: SteinerTree<SimpleGraph, i64>,
51 num_source_vertices: usize,
54 num_source_edges: usize,
57 target_to_source_edge: Vec<Option<usize>>,
60 target_to_include_vertex: Vec<Option<usize>>,
64}
65
66impl ReductionResult for ReductionPCSFToSteinerTree {
67 type Source = PrizeCollectingSteinerForest<SimpleGraph, i64>;
68 type Target = SteinerTree<SimpleGraph, i64>;
69
70 fn target_problem(&self) -> &SteinerTree<SimpleGraph, i64> {
71 &self.target
72 }
73
74 fn extract_solution(
75 &self,
76 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
77 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
78 crate::rules::traits::validate_target_witness(
79 self.target_problem(),
80 target_solution,
81 |value| value.is_valid(),
82 "target edges do not form a Steiner tree",
83 )?;
84
85 Ok({
86 let n = self.num_source_vertices;
87 let m = self.num_source_edges;
88 let mut selected_vertices = vec![false; n];
89 let mut selected_edges = vec![false; m];
90 let edges = self.target.graph().edges();
91
92 for (target_idx, &selected) in target_solution.iter().enumerate() {
95 if !selected {
96 continue;
97 }
98 if let Some(v) = self.target_to_include_vertex[target_idx] {
99 selected_vertices[v] = true;
100 } else if let Some(src_edge) = self.target_to_source_edge[target_idx] {
101 selected_edges[src_edge] = true;
102 let (u, v) = edges[target_idx];
105 selected_vertices[u] = true;
106 selected_vertices[v] = true;
107 }
108 }
109
110 (selected_vertices, selected_edges)
111 })
112 }
113}
114
115#[reduction(
116 transform = exact {
117 num_vertices = "num_vertices + num_vertices_with_prize + 1",
118 num_edges = "num_edges + num_vertices + 2 * num_vertices_with_prize",
119 num_terminals = "num_vertices_with_prize + 1",
120 }
121)]
122impl ReduceTo<SteinerTree<SimpleGraph, i64>> for PrizeCollectingSteinerForest<SimpleGraph, i64> {
123 type Result = ReductionPCSFToSteinerTree;
124
125 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
126 let n = self.num_vertices();
127 let m = self.num_edges();
128 let source_edges = self.graph().edges();
129 let source_edge_costs = self.edge_costs();
130 let source_prizes = self.vertex_prizes();
131 let beta = *self.beta();
132 let omega = *self.omega();
133
134 let prized: Vec<usize> = (0..n).filter(|&v| source_prizes[v] > 0).collect();
140 let k = prized.len();
141 let root = n;
142 let gadget_terminal = |gadget_pos: usize| -> usize { n + 1 + gadget_pos };
143
144 let target_num_vertices = n + 1 + k;
145 let mut target_edges: Vec<(usize, usize)> = Vec::with_capacity(m + n + 2 * k);
146 let mut target_edge_weights: Vec<i64> = Vec::with_capacity(m + n + 2 * k);
147 let mut target_to_source_edge: Vec<Option<usize>> = Vec::with_capacity(m + n + 2 * k);
148 let mut target_to_include_vertex: Vec<Option<usize>> = Vec::with_capacity(m + n + 2 * k);
149
150 for (idx, &(u, v)) in source_edges.iter().enumerate() {
152 target_edges.push((u, v));
153 target_edge_weights.push(source_edge_costs[idx]);
154 target_to_source_edge.push(Some(idx));
155 target_to_include_vertex.push(None);
156 }
157
158 for v in 0..n {
160 target_edges.push((v, root));
161 target_edge_weights.push(omega);
162 target_to_source_edge.push(None);
163 target_to_include_vertex.push(None);
164 }
165
166 for (gadget_pos, &v) in prized.iter().enumerate() {
169 let t_v = gadget_terminal(gadget_pos);
170 let include_cost =
171 omega.checked_add(1).ok_or_else(|| {
172 crate::rules::ReductionError::integer_overflow::<
173 Self,
174 SteinerTree<SimpleGraph, i64>,
175 >("forming the Steiner gadget inclusion cost")
176 })?;
177 let omit_cost = beta
178 .checked_mul(source_prizes[v])
179 .and_then(|penalty| penalty.checked_add(include_cost))
180 .ok_or_else(|| {
181 crate::rules::ReductionError::integer_overflow::<
182 Self,
183 SteinerTree<SimpleGraph, i64>,
184 >("forming the Steiner gadget omission cost")
185 })?;
186 target_edges.push((v, t_v));
188 target_edge_weights.push(include_cost);
189 target_to_source_edge.push(None);
190 target_to_include_vertex.push(Some(v));
191 target_edges.push((root, t_v));
193 target_edge_weights.push(omit_cost);
194 target_to_source_edge.push(None);
195 target_to_include_vertex.push(None);
196 }
197
198 let mut terminals: Vec<usize> = Vec::with_capacity(k + 1);
200 terminals.push(root);
201 for gadget_pos in 0..k {
202 terminals.push(gadget_terminal(gadget_pos));
203 }
204
205 let target_graph = SimpleGraph::new(target_num_vertices, target_edges);
206 let target =
207 SteinerTree::<SimpleGraph, i64>::new(target_graph, target_edge_weights, terminals);
208
209 Ok(ReductionPCSFToSteinerTree {
210 target,
211 num_source_vertices: n,
212 num_source_edges: m,
213 target_to_source_edge,
214 target_to_include_vertex,
215 })
216 }
217}
218
219#[cfg(feature = "example-db")]
220pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
221 use crate::example_db::specs::RuleExampleSpec;
222 use crate::export::SolutionPair;
223 use crate::solvers::BruteForce;
224
225 vec![RuleExampleSpec {
226 id: "prize_collecting_steiner_forest_to_steiner_tree",
227 build: || {
228 let source = PrizeCollectingSteinerForest::<SimpleGraph, i64>::new(
234 SimpleGraph::new(3, vec![(0, 1), (1, 2)]),
235 vec![5, 1, 5],
236 vec![10, 10],
237 1,
238 1,
239 )
240 .unwrap();
241 let reduction = <PrizeCollectingSteinerForest<SimpleGraph, i64> as ReduceTo<
242 SteinerTree<SimpleGraph, i64>,
243 >>::reduce_to(&source)
244 .expect("reduction should succeed");
245 let target = reduction.target_problem();
246 let target_config = BruteForce::new()
247 .solve(target)
248 .expect("canonical target evaluation must succeed")
249 .expect("canonical PCSF -> SteinerTree example must have an optimal target tree");
250 let source_config = reduction.extract_solution(&target_config).unwrap();
251 crate::example_db::specs::assemble_rule_example(
252 &source,
253 target,
254 vec![SolutionPair {
255 source_config: serde_json::to_value(source_config)
256 .expect("solution serialization must succeed"),
257 target_config: serde_json::to_value(target_config)
258 .expect("solution serialization must succeed"),
259 }],
260 )
261 },
262 }]
263}
264
265#[cfg(test)]
266#[path = "../unit_tests/rules/prizecollectingsteinerforest_steinertree.rs"]
267mod tests;