problemreductions/models/graph/
rural_postman.rs1use 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)]
56pub struct RuralPostman<G, W: WeightElement> {
57 graph: G,
59 edge_lengths: Vec<W>,
61 required_edges: Vec<usize>,
63}
64
65#[derive(Debug, Deserialize, crate::CreateSpec)]
66struct RuralPostmanCreateSpec {
67 #[create(codec = "edge-list")]
68 graph: Vec<(usize, usize)>,
69 num_vertices: Option<usize>,
70 #[create(codec = "comma-separated")]
71 edge_weights: Option<Vec<i64>>,
72 #[create(codec = "comma-separated")]
73 required_edges: Vec<usize>,
74}
75
76impl TryFrom<RuralPostmanCreateSpec> for RuralPostman<SimpleGraph, i64> {
77 type Error = crate::registry::ConstructionError;
78
79 fn try_from(spec: RuralPostmanCreateSpec) -> Result<Self, Self::Error> {
80 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
81 let edge_lengths = spec
82 .edge_weights
83 .unwrap_or_else(|| vec![1; graph.num_edges()]);
84 if edge_lengths.len() != graph.num_edges() {
85 return Err(format!(
86 "edge_weights has length {}, expected {}",
87 edge_lengths.len(),
88 graph.num_edges()
89 )
90 .into());
91 }
92 if let Some(&edge) = spec
93 .required_edges
94 .iter()
95 .find(|&&edge| edge >= graph.num_edges())
96 {
97 return Err(format!("required edge index {edge} is out of bounds").into());
98 }
99 Ok(Self::new(graph, edge_lengths, spec.required_edges))
100 }
101}
102
103fn simple_graph_from_create(
104 edges: Vec<(usize, usize)>,
105 num_vertices: Option<usize>,
106) -> Result<SimpleGraph, crate::registry::ConstructionError> {
107 if edges.is_empty() && num_vertices.is_none() {
108 return Err("num_vertices is required for an empty graph"
109 .to_string()
110 .into());
111 }
112 for (index, &(u, v)) in edges.iter().enumerate() {
113 if u == v {
114 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
115 }
116 }
117 let inferred = edges
118 .iter()
119 .flat_map(|&(u, v)| [u, v])
120 .max()
121 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
122 .transpose()?
123 .unwrap_or(0);
124 let num_vertices = num_vertices.unwrap_or(inferred);
125 if num_vertices < inferred {
126 return Err(format!(
127 "num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}"
128 )
129 .into());
130 }
131 Ok(SimpleGraph::new(num_vertices, edges))
132}
133
134impl<G: Graph, W: WeightElement> RuralPostman<G, W> {
135 pub fn new(graph: G, edge_lengths: Vec<W>, required_edges: Vec<usize>) -> Self {
141 assert_eq!(
142 edge_lengths.len(),
143 graph.num_edges(),
144 "edge_lengths length must match num_edges"
145 );
146 for &idx in &required_edges {
147 assert!(
148 idx < graph.num_edges(),
149 "required edge index {} out of bounds (graph has {} edges)",
150 idx,
151 graph.num_edges()
152 );
153 }
154 Self {
155 graph,
156 edge_lengths,
157 required_edges,
158 }
159 }
160
161 pub fn graph(&self) -> &G {
163 &self.graph
164 }
165
166 pub fn edge_lengths(&self) -> &[W] {
168 &self.edge_lengths
169 }
170
171 pub fn required_edges(&self) -> &[usize] {
173 &self.required_edges
174 }
175
176 pub fn num_vertices(&self) -> usize {
178 self.graph.num_vertices()
179 }
180
181 pub fn num_edges(&self) -> usize {
183 self.graph.num_edges()
184 }
185
186 pub fn num_required_edges(&self) -> usize {
188 self.required_edges.len()
189 }
190
191 pub fn set_weights(&mut self, weights: Vec<W>) {
193 assert_eq!(weights.len(), self.graph.num_edges());
194 self.edge_lengths = weights;
195 }
196
197 pub fn weights(&self) -> Vec<W> {
199 self.edge_lengths.clone()
200 }
201
202 pub fn is_weighted(&self) -> bool {
204 !W::IS_UNIT
205 }
206
207 pub fn is_valid_solution(
212 &self,
213 config: &[usize],
214 ) -> Result<Option<W::Sum>, crate::traits::EvaluationError> {
215 if config.len() != self.graph.num_edges() {
216 return Ok(None);
217 }
218
219 let edges = self.graph.edges();
220 let n = self.graph.num_vertices();
221
222 for &req_idx in &self.required_edges {
224 if config[req_idx] == 0 {
225 return Ok(None);
226 }
227 }
228
229 let mut degree = vec![0usize; n];
231 let mut has_edges = false;
232 for (idx, &mult) in config.iter().enumerate() {
233 if mult > 0 {
234 let (u, v) = edges[idx];
235 degree[u] += mult;
236 degree[v] += mult;
237 has_edges = true;
238 }
239 }
240
241 if !has_edges {
243 if self.required_edges.is_empty() {
244 return Ok(Some(W::Sum::zero()));
245 } else {
246 return Ok(None);
247 }
248 }
249
250 for &d in °ree {
252 if d % 2 != 0 {
253 return Ok(None);
254 }
255 }
256
257 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
260 let mut first_vertex = None;
261 for (idx, &mult) in config.iter().enumerate() {
262 if mult > 0 {
263 let (u, v) = edges[idx];
264 adj[u].push(v);
265 adj[v].push(u);
266 if first_vertex.is_none() {
267 first_vertex = Some(u);
268 }
269 }
270 }
271
272 let first = match first_vertex {
273 Some(v) => v,
274 None => {
275 if self.required_edges.is_empty() {
276 return Ok(Some(W::Sum::zero()));
277 } else {
278 return Ok(None);
279 }
280 }
281 };
282
283 let mut visited = vec![false; n];
284 let mut queue = VecDeque::new();
285 visited[first] = true;
286 queue.push_back(first);
287
288 while let Some(node) = queue.pop_front() {
289 for &neighbor in &adj[node] {
290 if !visited[neighbor] {
291 visited[neighbor] = true;
292 queue.push_back(neighbor);
293 }
294 }
295 }
296
297 for v in 0..n {
299 if degree[v] > 0 && !visited[v] {
300 return Ok(None);
301 }
302 }
303
304 let mut total = W::Sum::zero();
306 for (idx, &mult) in config.iter().enumerate() {
307 for _ in 0..mult {
308 total = W::checked_add_to_sum(
309 total,
310 self.edge_lengths[idx].to_sum(),
311 "summing rural postman edge lengths",
312 )?;
313 }
314 }
315
316 Ok(Some(total))
317 }
318}
319
320impl<G, W> Problem for RuralPostman<G, W>
321where
322 G: Graph + crate::variant::VariantParam,
323 W: WeightElement + crate::variant::VariantParam,
324{
325 const NAME: &'static str = "RuralPostman";
326 type Solution = Vec<usize>;
327 type Value = Min<W::Sum>;
328
329 crate::problem_parameters![
330 ("num_edges", num_edges),
331 ("num_required_edges", num_required_edges),
332 ("num_vertices", num_vertices),
333 ];
334
335 fn variant() -> Vec<(&'static str, &'static str)> {
336 crate::variant_params![G, W]
337 }
338
339 fn evaluate(
340 &self,
341 config: &Self::Solution,
342 ) -> Result<Min<W::Sum>, crate::traits::EvaluationError> {
343 if config.len() != self.graph.num_edges() {
344 return Err(crate::traits::EvaluationError::InvalidConfiguration(
345 "edge-multiplicity vector length does not match the graph".into(),
346 ));
347 }
348 Ok(Min(self.is_valid_solution(config)?))
349 }
350}
351
352impl<G, W> crate::solvers::BruteForceProblem for RuralPostman<G, W>
353where
354 G: Graph + crate::variant::VariantParam,
355 W: WeightElement + crate::variant::VariantParam,
356{
357 fn dimensions(&self) -> Vec<usize> {
358 vec![3; self.graph.num_edges()]
359 }
360}
361
362crate::declare_variants! {
363 default RuralPostman<SimpleGraph, i64> => "2^num_vertices * num_vertices^2" create RuralPostmanCreateSpec,
364}
365
366crate::register_brute_force! {
367 RuralPostman<SimpleGraph, i64>,
368}
369
370#[cfg(feature = "example-db")]
371pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
372 use crate::topology::SimpleGraph;
373 let graph = SimpleGraph::new(
376 6,
377 vec![
378 (0, 1),
379 (1, 2),
380 (2, 3),
381 (3, 4),
382 (4, 5),
383 (5, 0),
384 (0, 3),
385 (1, 4),
386 ],
387 );
388 vec![crate::example_db::specs::ModelExampleSpec {
389 id: "rural_postman",
390 instance: Box::new(RuralPostman::new(
391 graph,
392 vec![1, 1, 1, 1, 1, 1, 2, 2],
393 vec![0, 2, 4],
394 )),
395 optimal_config: serde_json::json!(vec![1, 1, 1, 1, 1, 1, 0, 0]),
396 optimal_value: serde_json::json!(6),
397 }]
398}
399
400#[cfg(test)]
401#[path = "../../unit_tests/models/graph/rural_postman.rs"]
402mod tests;