1use crate::registry::{CreateSpec, 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 category: crate::registry::ProblemCategory::Misc,
21 module_path: module_path!(),
22 description: "Find a closed walk that traverses each required directed arc and minimizes total length",
23 fields: StackerCraneCreateSpec::FIELDS,
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(try_from = "StackerCraneDef")]
36pub struct StackerCrane {
37 num_vertices: usize,
38 arcs: Vec<(usize, usize)>,
39 edges: Vec<(usize, usize)>,
40 arc_lengths: Vec<i64>,
41 edge_lengths: Vec<i64>,
42}
43
44#[derive(Debug, Deserialize, crate::CreateSpec)]
45struct StackerCraneCreateSpec {
46 #[create(codec = "arc-list")]
48 arcs: Vec<(usize, usize)>,
49 #[create(name = "graph", codec = "edge-list")]
51 edges: Vec<(usize, usize)>,
52 num_vertices: Option<usize>,
54 #[create(codec = "comma-separated")]
56 arc_lengths: Option<Vec<i64>>,
57 #[create(codec = "comma-separated")]
59 edge_lengths: Option<Vec<i64>>,
60}
61
62impl TryFrom<StackerCraneCreateSpec> for StackerCrane {
63 type Error = crate::registry::ConstructionError;
64
65 fn try_from(spec: StackerCraneCreateSpec) -> Result<Self, Self::Error> {
66 if spec.arcs.is_empty() {
67 return Err("arcs must be non-empty".to_string().into());
68 }
69 if spec.edges.is_empty() && spec.num_vertices.is_none() {
70 return Err("num_vertices is required for an empty graph"
71 .to_string()
72 .into());
73 }
74 for (index, &(u, v)) in spec.edges.iter().enumerate() {
75 if u == v {
76 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
77 }
78 }
79 let inferred_arcs = inferred_vertex_count(&spec.arcs)?;
80 let inferred_edges = inferred_vertex_count(&spec.edges)?;
81 let num_vertices = match spec.num_vertices {
82 Some(count) => count,
83 None if inferred_arcs == inferred_edges => inferred_arcs,
84 None => {
85 return Err(format!(
86 "directed and undirected inputs infer different vertex counts ({inferred_arcs} and {inferred_edges}); provide num_vertices"
87 ).into())
88 }
89 };
90 if num_vertices < inferred_arcs || num_vertices < inferred_edges {
91 return Err(format!(
92 "num_vertices {num_vertices} is too small for the provided endpoints"
93 )
94 .into());
95 }
96 let arc_lengths = spec.arc_lengths.unwrap_or_else(|| vec![1; spec.arcs.len()]);
97 let edge_lengths = spec
98 .edge_lengths
99 .unwrap_or_else(|| vec![1; spec.edges.len()]);
100 Self::try_new(
101 num_vertices,
102 spec.arcs,
103 spec.edges,
104 arc_lengths,
105 edge_lengths,
106 )
107 }
108}
109
110fn inferred_vertex_count(
111 pairs: &[(usize, usize)],
112) -> Result<usize, crate::registry::ConstructionError> {
113 Ok(pairs
114 .iter()
115 .flat_map(|&(u, v)| [u, v])
116 .max()
117 .map(|vertex| {
118 vertex
119 .checked_add(1)
120 .ok_or("vertex count overflows usize".to_string())
121 })
122 .transpose()
123 .map(|count| count.unwrap_or(0))?)
124}
125
126impl StackerCrane {
127 pub fn new(
134 num_vertices: usize,
135 arcs: Vec<(usize, usize)>,
136 edges: Vec<(usize, usize)>,
137 arc_lengths: Vec<i64>,
138 edge_lengths: Vec<i64>,
139 ) -> Self {
140 Self::try_new(num_vertices, arcs, edges, arc_lengths, edge_lengths)
141 .unwrap_or_else(|message| panic!("{message}"))
142 }
143
144 pub fn try_new(
146 num_vertices: usize,
147 arcs: Vec<(usize, usize)>,
148 edges: Vec<(usize, usize)>,
149 arc_lengths: Vec<i64>,
150 edge_lengths: Vec<i64>,
151 ) -> Result<Self, crate::registry::ConstructionError> {
152 if arc_lengths.len() != arcs.len() {
153 return Err("arc_lengths length must match arcs length"
154 .to_string()
155 .into());
156 }
157 if edge_lengths.len() != edges.len() {
158 return Err("edge_lengths length must match edges length"
159 .to_string()
160 .into());
161 }
162 for (arc_index, &(tail, head)) in arcs.iter().enumerate() {
163 if tail >= num_vertices || head >= num_vertices {
164 return Err(format!(
165 "arc {arc_index} endpoint out of range for {num_vertices} vertices"
166 )
167 .into());
168 }
169 }
170 for (edge_index, &(u, v)) in edges.iter().enumerate() {
171 if u >= num_vertices || v >= num_vertices {
172 return Err(format!(
173 "edge {edge_index} endpoint out of range for {num_vertices} vertices"
174 )
175 .into());
176 }
177 }
178 for (arc_index, &length) in arc_lengths.iter().enumerate() {
179 if length < 0 {
180 return Err(format!("arc length {arc_index} must be nonnegative").into());
181 }
182 }
183 for (edge_index, &length) in edge_lengths.iter().enumerate() {
184 if length < 0 {
185 return Err(format!("edge length {edge_index} must be nonnegative").into());
186 }
187 }
188
189 Ok(Self {
190 num_vertices,
191 arcs,
192 edges,
193 arc_lengths,
194 edge_lengths,
195 })
196 }
197
198 pub fn num_vertices(&self) -> usize {
200 self.num_vertices
201 }
202
203 pub fn arcs(&self) -> &[(usize, usize)] {
205 &self.arcs
206 }
207
208 pub fn edges(&self) -> &[(usize, usize)] {
210 &self.edges
211 }
212
213 pub fn arc_lengths(&self) -> &[i64] {
215 &self.arc_lengths
216 }
217
218 pub fn edge_lengths(&self) -> &[i64] {
220 &self.edge_lengths
221 }
222
223 pub fn num_arcs(&self) -> usize {
225 self.arcs.len()
226 }
227
228 pub fn num_edges(&self) -> usize {
230 self.edges.len()
231 }
232
233 fn is_arc_permutation(&self, config: &[usize]) -> bool {
234 if config.len() != self.num_arcs() {
235 return false;
236 }
237
238 let mut seen = vec![false; self.num_arcs()];
239 for &arc_index in config {
240 if arc_index >= self.num_arcs() || seen[arc_index] {
241 return false;
242 }
243 seen[arc_index] = true;
244 }
245
246 true
247 }
248
249 fn mixed_graph_adjacency(&self) -> Vec<Vec<(usize, i64)>> {
250 let mut adjacency = vec![Vec::new(); self.num_vertices];
251
252 for (&(tail, head), &length) in self.arcs.iter().zip(&self.arc_lengths) {
253 adjacency[tail].push((head, length));
254 }
255
256 for (&(u, v), &length) in self.edges.iter().zip(&self.edge_lengths) {
257 adjacency[u].push((v, length));
258 adjacency[v].push((u, length));
259 }
260
261 adjacency
262 }
263
264 fn shortest_path_length(
265 &self,
266 adjacency: &[Vec<(usize, i64)>],
267 source: usize,
268 target: usize,
269 ) -> Option<i64> {
270 if source == target {
271 return Some(0);
272 }
273
274 let mut dist = vec![i64::MAX; self.num_vertices];
275 let mut heap = BinaryHeap::new();
276 dist[source] = 0;
277 heap.push((Reverse(0i64), source));
278
279 while let Some((Reverse(cost), node)) = heap.pop() {
280 if cost > dist[node] {
281 continue;
282 }
283 if node == target {
284 return Some(cost);
285 }
286
287 for &(next, length) in &adjacency[node] {
288 let next_cost = cost.checked_add(length)?;
289 if next_cost < dist[next] {
290 dist[next] = next_cost;
291 heap.push((Reverse(next_cost), next));
292 }
293 }
294 }
295
296 None
297 }
298
299 pub fn closed_walk_length(&self, config: &[usize]) -> Option<i64> {
304 if !self.is_arc_permutation(config) {
305 return None;
306 }
307 if config.is_empty() {
308 return Some(0);
309 }
310
311 let adjacency = self.mixed_graph_adjacency();
312 let mut total = 0i64;
313
314 for position in 0..config.len() {
315 let arc_index = config[position];
316 let next_arc_index = config[(position + 1) % config.len()];
317 let (_, arc_head) = self.arcs[arc_index];
318 let (next_arc_tail, _) = self.arcs[next_arc_index];
319
320 total = total.checked_add(self.arc_lengths[arc_index])?;
321 total = total.checked_add(self.shortest_path_length(
322 &adjacency,
323 arc_head,
324 next_arc_tail,
325 )?)?;
326 }
327
328 Some(total)
329 }
330}
331
332impl Problem for StackerCrane {
333 const NAME: &'static str = "StackerCrane";
334 type Solution = Vec<usize>;
335 type Value = Min<i64>;
336
337 crate::problem_parameters![
338 ("num_arcs", num_arcs),
339 ("num_edges", num_edges),
340 ("num_vertices", num_vertices),
341 ];
342
343 fn variant() -> Vec<(&'static str, &'static str)> {
344 crate::variant_params![]
345 }
346
347 fn evaluate(
348 &self,
349 config: &Self::Solution,
350 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
351 if config.len() != self.num_arcs() {
352 return Err(crate::traits::EvaluationError::InvalidConfiguration(
353 "arc ordering length does not match the required arcs".into(),
354 ));
355 }
356 if config.iter().any(|&arc| arc >= self.num_arcs()) {
357 return Err(crate::traits::EvaluationError::InvalidConfiguration(
358 "arc ordering contains an out-of-range arc".into(),
359 ));
360 }
361 Ok({
362 match self.closed_walk_length(config) {
363 Some(total) => Min(Some(total)),
364 None => Min(None),
365 }
366 })
367 }
368}
369
370impl crate::solvers::BruteForceProblem for StackerCrane {
371 fn dimensions(&self) -> Vec<usize> {
372 vec![self.num_arcs(); self.num_arcs()]
373 }
374}
375
376crate::declare_variants! {
377 default StackerCrane => "num_vertices^2 * 2^num_arcs" create StackerCraneCreateSpec,
378}
379
380crate::register_brute_force! {
381 StackerCrane,
382}
383
384#[derive(Debug, Clone, Deserialize)]
385struct StackerCraneDef {
386 num_vertices: usize,
387 arcs: Vec<(usize, usize)>,
388 edges: Vec<(usize, usize)>,
389 arc_lengths: Vec<i64>,
390 edge_lengths: Vec<i64>,
391}
392
393impl TryFrom<StackerCraneDef> for StackerCrane {
394 type Error = crate::registry::ConstructionError;
395
396 fn try_from(value: StackerCraneDef) -> Result<Self, Self::Error> {
397 Self::try_new(
398 value.num_vertices,
399 value.arcs,
400 value.edges,
401 value.arc_lengths,
402 value.edge_lengths,
403 )
404 }
405}
406
407#[cfg(feature = "example-db")]
408pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
409 vec![crate::example_db::specs::ModelExampleSpec {
410 id: "stacker_crane",
411 instance: Box::new(StackerCrane::new(
412 6,
413 vec![(0, 4), (2, 5), (5, 1), (3, 0), (4, 3)],
414 vec![(0, 1), (1, 2), (2, 3), (3, 5), (4, 5), (0, 3), (1, 5)],
415 vec![3, 4, 2, 5, 3],
416 vec![2, 1, 3, 2, 1, 4, 3],
417 )),
418 optimal_config: serde_json::json!(vec![0, 2, 1, 4, 3]),
419 optimal_value: serde_json::json!(20),
420 }]
421}
422
423#[cfg(test)]
424#[path = "../../unit_tests/models/misc/stacker_crane.rs"]
425mod tests;