1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Min, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "RuralPostman",
17 display_name: "Rural Postman",
18 aliases: &["RPP"],
19 dimensions: &[
20 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21 VariantDimension::new("weight", "i64", &["i64"]),
22 ],
23 category: crate::registry::ProblemCategory::Graph,
24 module_path: module_path!(),
25 description: "Find a minimum-cost circuit covering all required edges (Rural Postman Problem)",
26 fields: RuralPostmanCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(try_from = "RuralPostmanData<G, W>")]
57#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
58pub struct RuralPostman<G, W: WeightElement> {
59 graph: G,
61 edge_lengths: Vec<W>,
63 required_edges: Vec<usize>,
65}
66
67#[derive(Deserialize)]
68#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
69struct RuralPostmanData<G, W: WeightElement> {
70 graph: G,
71 edge_lengths: Vec<W>,
72 required_edges: Vec<usize>,
73}
74
75impl<G, W> TryFrom<RuralPostmanData<G, W>> for RuralPostman<G, W>
76where
77 G: Graph,
78 W: WeightElement,
79{
80 type Error = crate::registry::ConstructionError;
81
82 fn try_from(data: RuralPostmanData<G, W>) -> Result<Self, Self::Error> {
83 Self::try_new(data.graph, data.edge_lengths, data.required_edges)
84 }
85}
86
87#[derive(Debug, Deserialize, crate::CreateSpec)]
88struct RuralPostmanCreateSpec {
89 #[create(codec = "edge-list")]
90 graph: Vec<(usize, usize)>,
91 num_vertices: Option<usize>,
92 #[create(codec = "comma-separated")]
93 edge_weights: Option<Vec<i64>>,
94 #[create(codec = "comma-separated")]
95 required_edges: Vec<usize>,
96}
97
98impl TryFrom<RuralPostmanCreateSpec> for RuralPostman<SimpleGraph, i64> {
99 type Error = crate::registry::ConstructionError;
100
101 fn try_from(spec: RuralPostmanCreateSpec) -> Result<Self, Self::Error> {
102 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
103 let edge_lengths = spec
104 .edge_weights
105 .unwrap_or_else(|| vec![1; graph.num_edges()]);
106 Self::try_new(graph, edge_lengths, spec.required_edges)
107 }
108}
109
110fn simple_graph_from_create(
111 edges: Vec<(usize, usize)>,
112 num_vertices: Option<usize>,
113) -> Result<SimpleGraph, crate::registry::ConstructionError> {
114 if edges.is_empty() && num_vertices.is_none() {
115 return Err("num_vertices is required for an empty graph"
116 .to_string()
117 .into());
118 }
119 for (index, &(u, v)) in edges.iter().enumerate() {
120 if u == v {
121 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
122 }
123 }
124 let inferred = edges
125 .iter()
126 .flat_map(|&(u, v)| [u, v])
127 .max()
128 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
129 .transpose()?
130 .unwrap_or(0);
131 let num_vertices = num_vertices.unwrap_or(inferred);
132 if num_vertices < inferred {
133 return Err(format!(
134 "num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}"
135 )
136 .into());
137 }
138 Ok(SimpleGraph::new(num_vertices, edges))
139}
140
141impl<G: Graph, W: WeightElement> RuralPostman<G, W> {
142 pub fn new(graph: G, edge_lengths: Vec<W>, required_edges: Vec<usize>) -> Self {
148 Self::try_new(graph, edge_lengths, required_edges).unwrap_or_else(|error| panic!("{error}"))
149 }
150
151 fn try_new(
152 graph: G,
153 edge_lengths: Vec<W>,
154 required_edges: Vec<usize>,
155 ) -> Result<Self, crate::registry::ConstructionError> {
156 Self::check_weights(&graph, &edge_lengths)?;
157 for &idx in &required_edges {
158 if idx >= graph.num_edges() {
159 return Err(format!(
160 "required edge index {} out of bounds (graph has {} edges)",
161 idx,
162 graph.num_edges()
163 )
164 .into());
165 }
166 }
167 Ok(Self {
168 graph,
169 edge_lengths,
170 required_edges,
171 })
172 }
173
174 pub fn graph(&self) -> &G {
176 &self.graph
177 }
178
179 pub fn edge_lengths(&self) -> &[W] {
181 &self.edge_lengths
182 }
183
184 pub fn required_edges(&self) -> &[usize] {
186 &self.required_edges
187 }
188
189 pub fn num_vertices(&self) -> usize {
191 self.graph.num_vertices()
192 }
193
194 pub fn num_edges(&self) -> usize {
196 self.graph.num_edges()
197 }
198
199 pub fn num_required_edges(&self) -> usize {
201 self.required_edges.len()
202 }
203
204 pub fn set_weights(&mut self, weights: Vec<W>) {
206 Self::check_weights(&self.graph, &weights).unwrap_or_else(|error| panic!("{error}"));
207 self.edge_lengths = weights;
208 }
209
210 fn check_weights(graph: &G, weights: &[W]) -> Result<(), crate::registry::ConstructionError> {
211 if weights.len() != graph.num_edges() {
212 return Err(crate::registry::ConstructionError::length_mismatch(
213 "weights",
214 weights.len(),
215 graph.num_edges(),
216 ));
217 }
218 Ok(())
219 }
220
221 pub fn weights(&self) -> Vec<W> {
223 self.edge_lengths.clone()
224 }
225
226 pub fn is_weighted(&self) -> bool {
228 !W::IS_UNIT
229 }
230
231 pub fn is_valid_solution(
236 &self,
237 config: &[usize],
238 ) -> Result<Option<W::Sum>, crate::traits::EvaluationError> {
239 if config.len() != self.graph.num_edges() {
240 return Ok(None);
241 }
242
243 let edges = self.graph.edges();
244 let n = self.graph.num_vertices();
245
246 for &req_idx in &self.required_edges {
248 if config[req_idx] == 0 {
249 return Ok(None);
250 }
251 }
252
253 let mut degree = vec![0usize; n];
255 let mut has_edges = false;
256 for (idx, &mult) in config.iter().enumerate() {
257 if mult > 0 {
258 let (u, v) = edges[idx];
259 degree[u] += mult;
260 degree[v] += mult;
261 has_edges = true;
262 }
263 }
264
265 if !has_edges {
267 if self.required_edges.is_empty() {
268 return Ok(Some(W::Sum::zero()));
269 } else {
270 return Ok(None);
271 }
272 }
273
274 for &d in °ree {
276 if d % 2 != 0 {
277 return Ok(None);
278 }
279 }
280
281 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
284 let mut first_vertex = None;
285 for (idx, &mult) in config.iter().enumerate() {
286 if mult > 0 {
287 let (u, v) = edges[idx];
288 adj[u].push(v);
289 adj[v].push(u);
290 if first_vertex.is_none() {
291 first_vertex = Some(u);
292 }
293 }
294 }
295
296 let first = match first_vertex {
297 Some(v) => v,
298 None => {
299 if self.required_edges.is_empty() {
300 return Ok(Some(W::Sum::zero()));
301 } else {
302 return Ok(None);
303 }
304 }
305 };
306
307 let mut visited = vec![false; n];
308 let mut queue = VecDeque::new();
309 visited[first] = true;
310 queue.push_back(first);
311
312 while let Some(node) = queue.pop_front() {
313 for &neighbor in &adj[node] {
314 if !visited[neighbor] {
315 visited[neighbor] = true;
316 queue.push_back(neighbor);
317 }
318 }
319 }
320
321 for v in 0..n {
323 if degree[v] > 0 && !visited[v] {
324 return Ok(None);
325 }
326 }
327
328 let mut total = W::Sum::zero();
330 for (idx, &mult) in config.iter().enumerate() {
331 for _ in 0..mult {
332 total = W::checked_add_to_sum(
333 total,
334 self.edge_lengths[idx].to_sum(),
335 "summing rural postman edge lengths",
336 )?;
337 }
338 }
339
340 Ok(Some(total))
341 }
342}
343
344impl<G, W> Problem for RuralPostman<G, W>
345where
346 G: Graph + crate::variant::VariantParam,
347 W: WeightElement + crate::variant::VariantParam,
348{
349 const NAME: &'static str = "RuralPostman";
350 type Solution = Vec<usize>;
351 type Value = Min<W::Sum>;
352
353 crate::problem_parameters![
354 ("num_edges", num_edges),
355 ("num_required_edges", num_required_edges),
356 ("num_vertices", num_vertices),
357 ];
358
359 fn variant() -> Vec<(&'static str, &'static str)> {
360 crate::variant_params![G, W]
361 }
362
363 fn evaluate(
364 &self,
365 config: &Self::Solution,
366 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
367 if config.len() != self.graph.num_edges() {
368 return Err(crate::traits::EvaluationError::InvalidConfiguration(
369 "edge-multiplicity vector length does not match the graph".into(),
370 ));
371 }
372 Ok(Min(self.is_valid_solution(config)?))
373 }
374}
375
376impl<G, W> crate::solvers::BruteForceProblem for RuralPostman<G, W>
377where
378 G: Graph + crate::variant::VariantParam,
379 W: WeightElement + crate::variant::VariantParam,
380{
381 fn dimensions(&self) -> Vec<usize> {
382 vec![3; self.graph.num_edges()]
383 }
384}
385
386crate::declare_variants! {
387 default RuralPostman<SimpleGraph, i64> => "2^num_vertices * num_vertices^2" create RuralPostmanCreateSpec,
388}
389
390crate::register_brute_force! {
391 RuralPostman<SimpleGraph, i64>,
392}
393
394#[cfg(feature = "example-db")]
395pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
396 use crate::topology::SimpleGraph;
397 let graph = SimpleGraph::new(
400 6,
401 vec![
402 (0, 1),
403 (1, 2),
404 (2, 3),
405 (3, 4),
406 (4, 5),
407 (5, 0),
408 (0, 3),
409 (1, 4),
410 ],
411 );
412 vec![crate::example_db::specs::ModelExampleSpec {
413 id: "rural_postman",
414 instance: Box::new(RuralPostman::new(
415 graph,
416 vec![1, 1, 1, 1, 1, 1, 2, 2],
417 vec![0, 2, 4],
418 )),
419 optimal_config: serde_json::json!(vec![1, 1, 1, 1, 1, 1, 0, 0]),
420 optimal_value: serde_json::json!(6),
421 }]
422}
423
424#[cfg(test)]
425#[path = "../../unit_tests/models/graph/rural_postman.rs"]
426mod tests;
427
428crate::decision_problem_meta!(RuralPostman<SimpleGraph, i64>, "DecisionRuralPostman");
429crate::register_decision_variant!(
430 RuralPostman<SimpleGraph, i64>, "DecisionRuralPostman", "2^num_vertices * num_vertices^2", &[],
431 "Does a feasible solution have objective value <= the bound?",
432 category: crate::registry::ProblemCategory::Graph,
433 dims: [
434 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
435 VariantDimension::new("weight", "i64", &["i64"]),
436 ],
437 fields: [
438 crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Graph edges as comma-separated vertex pairs." },
439 crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices, including isolated vertices." },
440 crate::registry::FieldInfo { name: "edge_weights", type_name: "Vec<i64>", description: "Weights for each edge in graph order." },
441 crate::registry::FieldInfo { name: "required_edges", type_name: "Vec<usize>", description: "Indices of edges that the route must traverse." },
442 crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values <= this bound" },
443 ],
444 decode: |_, indices: Vec<usize>| indices
445);
446
447#[cfg(feature = "example-db")]
448pub(crate) fn decision_canonical_rule_example_specs(
449) -> Vec<crate::example_db::specs::RuleExampleSpec> {
450 vec![crate::example_db::specs::RuleExampleSpec {
451 id: "decision_rural_postman_to_rural_postman",
452 build: || {
453 let graph = SimpleGraph::new(
454 6,
455 vec![
456 (0, 1),
457 (1, 2),
458 (2, 3),
459 (3, 4),
460 (4, 5),
461 (5, 0),
462 (0, 3),
463 (1, 4),
464 ],
465 );
466 let source = crate::models::decision::Decision::new(
467 RuralPostman::new(graph, vec![1, 1, 1, 1, 1, 1, 2, 2], vec![0, 2, 4]),
468 6,
469 );
470 let witness = serde_json::json!(vec![1, 1, 1, 1, 1, 1, 0, 0]);
471 crate::example_db::specs::rule_example_with_witness::<_, RuralPostman<SimpleGraph, i64>>(
472 source,
473 crate::export::SolutionPair {
474 source_config: witness.clone(),
475 target_config: witness,
476 },
477 )
478 },
479 }]
480}