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(crate::registry::ConstructionError::length_mismatch(
154 "arc_lengths",
155 arc_lengths.len(),
156 arcs.len(),
157 ));
158 }
159 if edge_lengths.len() != edges.len() {
160 return Err(crate::registry::ConstructionError::length_mismatch(
161 "edge_lengths",
162 edge_lengths.len(),
163 edges.len(),
164 ));
165 }
166 for (arc_index, &(tail, head)) in arcs.iter().enumerate() {
167 if tail >= num_vertices || head >= num_vertices {
168 return Err(format!(
169 "arc {arc_index} endpoint out of range for {num_vertices} vertices"
170 )
171 .into());
172 }
173 }
174 for (edge_index, &(u, v)) in edges.iter().enumerate() {
175 if u >= num_vertices || v >= num_vertices {
176 return Err(format!(
177 "edge {edge_index} endpoint out of range for {num_vertices} vertices"
178 )
179 .into());
180 }
181 }
182 for (arc_index, &length) in arc_lengths.iter().enumerate() {
183 if length < 0 {
184 return Err(format!("arc length {arc_index} must be nonnegative").into());
185 }
186 }
187 for (edge_index, &length) in edge_lengths.iter().enumerate() {
188 if length < 0 {
189 return Err(format!("edge length {edge_index} must be nonnegative").into());
190 }
191 }
192
193 Ok(Self {
194 num_vertices,
195 arcs,
196 edges,
197 arc_lengths,
198 edge_lengths,
199 })
200 }
201
202 pub fn num_vertices(&self) -> usize {
204 self.num_vertices
205 }
206
207 pub fn arcs(&self) -> &[(usize, usize)] {
209 &self.arcs
210 }
211
212 pub fn edges(&self) -> &[(usize, usize)] {
214 &self.edges
215 }
216
217 pub fn arc_lengths(&self) -> &[i64] {
219 &self.arc_lengths
220 }
221
222 pub fn edge_lengths(&self) -> &[i64] {
224 &self.edge_lengths
225 }
226
227 pub fn num_arcs(&self) -> usize {
229 self.arcs.len()
230 }
231
232 pub fn num_edges(&self) -> usize {
234 self.edges.len()
235 }
236
237 fn is_arc_permutation(&self, config: &[usize]) -> bool {
238 if config.len() != self.num_arcs() {
239 return false;
240 }
241
242 let mut seen = vec![false; self.num_arcs()];
243 for &arc_index in config {
244 if arc_index >= self.num_arcs() || seen[arc_index] {
245 return false;
246 }
247 seen[arc_index] = true;
248 }
249
250 true
251 }
252
253 fn mixed_graph_adjacency(&self) -> Vec<Vec<(usize, i64)>> {
254 let mut adjacency = vec![Vec::new(); self.num_vertices];
255
256 for (&(tail, head), &length) in self.arcs.iter().zip(&self.arc_lengths) {
257 adjacency[tail].push((head, length));
258 }
259
260 for (&(u, v), &length) in self.edges.iter().zip(&self.edge_lengths) {
261 adjacency[u].push((v, length));
262 adjacency[v].push((u, length));
263 }
264
265 adjacency
266 }
267
268 fn shortest_path_length(
269 &self,
270 adjacency: &[Vec<(usize, i64)>],
271 source: usize,
272 target: usize,
273 ) -> Option<i64> {
274 if source == target {
275 return Some(0);
276 }
277
278 let mut dist = vec![i64::MAX; self.num_vertices];
279 let mut heap = BinaryHeap::new();
280 dist[source] = 0;
281 heap.push((Reverse(0i64), source));
282
283 while let Some((Reverse(cost), node)) = heap.pop() {
284 if cost > dist[node] {
285 continue;
286 }
287 if node == target {
288 return Some(cost);
289 }
290
291 for &(next, length) in &adjacency[node] {
292 let next_cost = cost.checked_add(length)?;
293 if next_cost < dist[next] {
294 dist[next] = next_cost;
295 heap.push((Reverse(next_cost), next));
296 }
297 }
298 }
299
300 None
301 }
302
303 pub fn closed_walk_length(&self, config: &[usize]) -> Option<i64> {
308 if !self.is_arc_permutation(config) {
309 return None;
310 }
311 if config.is_empty() {
312 return Some(0);
313 }
314
315 let adjacency = self.mixed_graph_adjacency();
316 let mut total = 0i64;
317
318 for position in 0..config.len() {
319 let arc_index = config[position];
320 let next_arc_index = config[(position + 1) % config.len()];
321 let (_, arc_head) = self.arcs[arc_index];
322 let (next_arc_tail, _) = self.arcs[next_arc_index];
323
324 total = total.checked_add(self.arc_lengths[arc_index])?;
325 total = total.checked_add(self.shortest_path_length(
326 &adjacency,
327 arc_head,
328 next_arc_tail,
329 )?)?;
330 }
331
332 Some(total)
333 }
334}
335
336impl Problem for StackerCrane {
337 const NAME: &'static str = "StackerCrane";
338 type Solution = Vec<usize>;
339 type Value = Min<i64>;
340
341 crate::problem_parameters![
342 ("num_arcs", num_arcs),
343 ("num_edges", num_edges),
344 ("num_vertices", num_vertices),
345 ];
346
347 fn variant() -> Vec<(&'static str, &'static str)> {
348 crate::variant_params![]
349 }
350
351 fn evaluate(
352 &self,
353 config: &Self::Solution,
354 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
355 if config.len() != self.num_arcs() {
356 return Err(crate::traits::EvaluationError::InvalidConfiguration(
357 "arc ordering length does not match the required arcs".into(),
358 ));
359 }
360 if config.iter().any(|&arc| arc >= self.num_arcs()) {
361 return Err(crate::traits::EvaluationError::InvalidConfiguration(
362 "arc ordering contains an out-of-range arc".into(),
363 ));
364 }
365 Ok({
366 match self.closed_walk_length(config) {
367 Some(total) => Min(Some(total)),
368 None => Min(None),
369 }
370 })
371 }
372}
373
374impl crate::solvers::BruteForceProblem for StackerCrane {
375 fn dimensions(&self) -> Vec<usize> {
376 vec![self.num_arcs(); self.num_arcs()]
377 }
378}
379
380crate::declare_variants! {
381 default StackerCrane => "num_vertices^2 * 2^num_arcs" create StackerCraneCreateSpec,
382}
383
384crate::register_brute_force! {
385 StackerCrane,
386}
387
388#[derive(Debug, Clone, Deserialize)]
389struct StackerCraneDef {
390 num_vertices: usize,
391 arcs: Vec<(usize, usize)>,
392 edges: Vec<(usize, usize)>,
393 arc_lengths: Vec<i64>,
394 edge_lengths: Vec<i64>,
395}
396
397impl TryFrom<StackerCraneDef> for StackerCrane {
398 type Error = crate::registry::ConstructionError;
399
400 fn try_from(value: StackerCraneDef) -> Result<Self, Self::Error> {
401 Self::try_new(
402 value.num_vertices,
403 value.arcs,
404 value.edges,
405 value.arc_lengths,
406 value.edge_lengths,
407 )
408 }
409}
410
411#[cfg(feature = "example-db")]
412pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
413 vec![crate::example_db::specs::ModelExampleSpec {
414 id: "stacker_crane",
415 instance: Box::new(StackerCrane::new(
416 6,
417 vec![(0, 4), (2, 5), (5, 1), (3, 0), (4, 3)],
418 vec![(0, 1), (1, 2), (2, 3), (3, 5), (4, 5), (0, 3), (1, 5)],
419 vec![3, 4, 2, 5, 3],
420 vec![2, 1, 3, 2, 1, 4, 3],
421 )),
422 optimal_config: serde_json::json!(vec![0, 2, 1, 4, 3]),
423 optimal_value: serde_json::json!(20),
424 }]
425}
426
427#[cfg(test)]
428#[path = "../../unit_tests/models/misc/stacker_crane.rs"]
429mod tests;
430
431crate::decision_problem_meta!(StackerCrane, "DecisionStackerCrane");
432crate::register_decision_variant!(
433 StackerCrane, "DecisionStackerCrane", "num_vertices^2 * 2^num_arcs", &[],
434 "Does a feasible solution have objective value <= the bound?",
435 category: crate::registry::ProblemCategory::Misc,
436 dims: [],
437 fields: [
438 crate::registry::FieldInfo { name: "arcs", type_name: "Vec<(usize,usize)>", description: "Required directed arcs." },
439 crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Undirected connector edges." },
440 crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Vertex count, needed to preserve isolated vertices." },
441 crate::registry::FieldInfo { name: "arc_lengths", type_name: "Vec<i64>", description: "Required-arc lengths; defaults to one per arc." },
442 crate::registry::FieldInfo { name: "edge_lengths", type_name: "Vec<i64>", description: "Connector-edge lengths; defaults to one per edge." },
443 crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values <= this bound" },
444 ],
445 decode: |_, indices: Vec<usize>| indices
446);
447
448#[cfg(feature = "example-db")]
449pub(crate) fn decision_canonical_rule_example_specs(
450) -> Vec<crate::example_db::specs::RuleExampleSpec> {
451 vec![crate::example_db::specs::RuleExampleSpec {
452 id: "decision_stacker_crane_to_stacker_crane",
453 build: || {
454 let source = crate::models::decision::Decision::new(
455 StackerCrane::new(
456 6,
457 vec![(0, 4), (2, 5), (5, 1), (3, 0), (4, 3)],
458 vec![(0, 1), (1, 2), (2, 3), (3, 5), (4, 5), (0, 3), (1, 5)],
459 vec![3, 4, 2, 5, 3],
460 vec![2, 1, 3, 2, 1, 4, 3],
461 ),
462 20,
463 );
464 let witness = serde_json::json!(vec![0, 2, 1, 4, 3]);
465 crate::example_db::specs::rule_example_with_witness::<_, StackerCrane>(
466 source,
467 crate::export::SolutionPair {
468 source_config: witness.clone(),
469 target_config: witness,
470 },
471 )
472 },
473 }]
474}