problemreductions/models/misc/
stacker_crane.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11use std::cmp::Reverse;
12use std::collections::BinaryHeap;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "StackerCrane",
17 display_name: "Stacker Crane",
18 aliases: &[],
19 dimensions: &[],
20 module_path: module_path!(),
21 description: "Find a closed walk that traverses each required directed arc and minimizes total length",
22 fields: &[
23 FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices in the mixed graph" },
24 FieldInfo { name: "arcs", type_name: "Vec<(usize, usize)>", description: "Required directed arcs that must be traversed" },
25 FieldInfo { name: "edges", type_name: "Vec<(usize, usize)>", description: "Undirected edges available for connector paths" },
26 FieldInfo { name: "arc_lengths", type_name: "Vec<i32>", description: "Nonnegative lengths of the required directed arcs" },
27 FieldInfo { name: "edge_lengths", type_name: "Vec<i32>", description: "Nonnegative lengths of the undirected connector edges" },
28 ],
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(try_from = "StackerCraneDef")]
41pub struct StackerCrane {
42 num_vertices: usize,
43 arcs: Vec<(usize, usize)>,
44 edges: Vec<(usize, usize)>,
45 arc_lengths: Vec<i32>,
46 edge_lengths: Vec<i32>,
47}
48
49impl StackerCrane {
50 pub fn new(
57 num_vertices: usize,
58 arcs: Vec<(usize, usize)>,
59 edges: Vec<(usize, usize)>,
60 arc_lengths: Vec<i32>,
61 edge_lengths: Vec<i32>,
62 ) -> Self {
63 Self::try_new(num_vertices, arcs, edges, arc_lengths, edge_lengths)
64 .unwrap_or_else(|message| panic!("{message}"))
65 }
66
67 pub fn try_new(
69 num_vertices: usize,
70 arcs: Vec<(usize, usize)>,
71 edges: Vec<(usize, usize)>,
72 arc_lengths: Vec<i32>,
73 edge_lengths: Vec<i32>,
74 ) -> Result<Self, String> {
75 if arc_lengths.len() != arcs.len() {
76 return Err("arc_lengths length must match arcs length".to_string());
77 }
78 if edge_lengths.len() != edges.len() {
79 return Err("edge_lengths length must match edges length".to_string());
80 }
81 for (arc_index, &(tail, head)) in arcs.iter().enumerate() {
82 if tail >= num_vertices || head >= num_vertices {
83 return Err(format!(
84 "arc {arc_index} endpoint out of range for {num_vertices} vertices"
85 ));
86 }
87 }
88 for (edge_index, &(u, v)) in edges.iter().enumerate() {
89 if u >= num_vertices || v >= num_vertices {
90 return Err(format!(
91 "edge {edge_index} endpoint out of range for {num_vertices} vertices"
92 ));
93 }
94 }
95 for (arc_index, &length) in arc_lengths.iter().enumerate() {
96 if length < 0 {
97 return Err(format!("arc length {arc_index} must be nonnegative"));
98 }
99 }
100 for (edge_index, &length) in edge_lengths.iter().enumerate() {
101 if length < 0 {
102 return Err(format!("edge length {edge_index} must be nonnegative"));
103 }
104 }
105
106 Ok(Self {
107 num_vertices,
108 arcs,
109 edges,
110 arc_lengths,
111 edge_lengths,
112 })
113 }
114
115 pub fn num_vertices(&self) -> usize {
117 self.num_vertices
118 }
119
120 pub fn arcs(&self) -> &[(usize, usize)] {
122 &self.arcs
123 }
124
125 pub fn edges(&self) -> &[(usize, usize)] {
127 &self.edges
128 }
129
130 pub fn arc_lengths(&self) -> &[i32] {
132 &self.arc_lengths
133 }
134
135 pub fn edge_lengths(&self) -> &[i32] {
137 &self.edge_lengths
138 }
139
140 pub fn num_arcs(&self) -> usize {
142 self.arcs.len()
143 }
144
145 pub fn num_edges(&self) -> usize {
147 self.edges.len()
148 }
149
150 fn is_arc_permutation(&self, config: &[usize]) -> bool {
151 if config.len() != self.num_arcs() {
152 return false;
153 }
154
155 let mut seen = vec![false; self.num_arcs()];
156 for &arc_index in config {
157 if arc_index >= self.num_arcs() || seen[arc_index] {
158 return false;
159 }
160 seen[arc_index] = true;
161 }
162
163 true
164 }
165
166 fn mixed_graph_adjacency(&self) -> Vec<Vec<(usize, i32)>> {
167 let mut adjacency = vec![Vec::new(); self.num_vertices];
168
169 for (&(tail, head), &length) in self.arcs.iter().zip(&self.arc_lengths) {
170 adjacency[tail].push((head, length));
171 }
172
173 for (&(u, v), &length) in self.edges.iter().zip(&self.edge_lengths) {
174 adjacency[u].push((v, length));
175 adjacency[v].push((u, length));
176 }
177
178 adjacency
179 }
180
181 fn shortest_path_length(
182 &self,
183 adjacency: &[Vec<(usize, i32)>],
184 source: usize,
185 target: usize,
186 ) -> Option<i64> {
187 if source == target {
188 return Some(0);
189 }
190
191 let mut dist = vec![i64::MAX; self.num_vertices];
192 let mut heap = BinaryHeap::new();
193 dist[source] = 0;
194 heap.push((Reverse(0i64), source));
195
196 while let Some((Reverse(cost), node)) = heap.pop() {
197 if cost > dist[node] {
198 continue;
199 }
200 if node == target {
201 return Some(cost);
202 }
203
204 for &(next, length) in &adjacency[node] {
205 let next_cost = cost.checked_add(i64::from(length))?;
206 if next_cost < dist[next] {
207 dist[next] = next_cost;
208 heap.push((Reverse(next_cost), next));
209 }
210 }
211 }
212
213 None
214 }
215
216 pub fn closed_walk_length(&self, config: &[usize]) -> Option<i32> {
221 if !self.is_arc_permutation(config) {
222 return None;
223 }
224 if config.is_empty() {
225 return Some(0);
226 }
227
228 let adjacency = self.mixed_graph_adjacency();
229 let mut total = 0i64;
230
231 for position in 0..config.len() {
232 let arc_index = config[position];
233 let next_arc_index = config[(position + 1) % config.len()];
234 let (_, arc_head) = self.arcs[arc_index];
235 let (next_arc_tail, _) = self.arcs[next_arc_index];
236
237 total = total.checked_add(i64::from(self.arc_lengths[arc_index]))?;
238 total = total.checked_add(self.shortest_path_length(
239 &adjacency,
240 arc_head,
241 next_arc_tail,
242 )?)?;
243 }
244
245 i32::try_from(total).ok()
246 }
247}
248
249impl Problem for StackerCrane {
250 const NAME: &'static str = "StackerCrane";
251 type Value = Min<i32>;
252
253 fn variant() -> Vec<(&'static str, &'static str)> {
254 crate::variant_params![]
255 }
256
257 fn dims(&self) -> Vec<usize> {
258 vec![self.num_arcs(); self.num_arcs()]
259 }
260
261 fn evaluate(&self, config: &[usize]) -> Min<i32> {
262 match self.closed_walk_length(config) {
263 Some(total) => Min(Some(total)),
264 None => Min(None),
265 }
266 }
267}
268
269crate::declare_variants! {
270 default StackerCrane => "num_vertices^2 * 2^num_arcs",
271}
272
273#[derive(Debug, Clone, Deserialize)]
274struct StackerCraneDef {
275 num_vertices: usize,
276 arcs: Vec<(usize, usize)>,
277 edges: Vec<(usize, usize)>,
278 arc_lengths: Vec<i32>,
279 edge_lengths: Vec<i32>,
280}
281
282impl TryFrom<StackerCraneDef> for StackerCrane {
283 type Error = String;
284
285 fn try_from(value: StackerCraneDef) -> Result<Self, Self::Error> {
286 Self::try_new(
287 value.num_vertices,
288 value.arcs,
289 value.edges,
290 value.arc_lengths,
291 value.edge_lengths,
292 )
293 }
294}
295
296#[cfg(feature = "example-db")]
297pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
298 vec![crate::example_db::specs::ModelExampleSpec {
299 id: "stacker_crane",
300 instance: Box::new(StackerCrane::new(
301 6,
302 vec![(0, 4), (2, 5), (5, 1), (3, 0), (4, 3)],
303 vec![(0, 1), (1, 2), (2, 3), (3, 5), (4, 5), (0, 3), (1, 5)],
304 vec![3, 4, 2, 5, 3],
305 vec![2, 1, 3, 2, 1, 4, 3],
306 )),
307 optimal_config: vec![0, 2, 1, 4, 3],
308 optimal_value: serde_json::json!(20),
309 }]
310}
311
312#[cfg(test)]
313#[path = "../../unit_tests/models/misc/stacker_crane.rs"]
314mod tests;