problemreductions/models/graph/
rural_postman.rs1use crate::registry::{FieldInfo, 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", "i32", &["i32"]),
22 ],
23 module_path: module_path!(),
24 description: "Find a minimum-cost circuit covering all required edges (Rural Postman Problem)",
25 fields: &[
26 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
27 FieldInfo { name: "edge_weights", type_name: "Vec<W>", description: "Edge lengths l(e) for each e in E" },
28 FieldInfo { name: "required_edges", type_name: "Vec<usize>", description: "Edge indices of the required subset E' ⊆ E" },
29 ],
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct RuralPostman<G, W: WeightElement> {
60 graph: G,
62 edge_lengths: Vec<W>,
64 required_edges: Vec<usize>,
66}
67
68impl<G: Graph, W: WeightElement> RuralPostman<G, W> {
69 pub fn new(graph: G, edge_lengths: Vec<W>, required_edges: Vec<usize>) -> Self {
75 assert_eq!(
76 edge_lengths.len(),
77 graph.num_edges(),
78 "edge_lengths length must match num_edges"
79 );
80 for &idx in &required_edges {
81 assert!(
82 idx < graph.num_edges(),
83 "required edge index {} out of bounds (graph has {} edges)",
84 idx,
85 graph.num_edges()
86 );
87 }
88 Self {
89 graph,
90 edge_lengths,
91 required_edges,
92 }
93 }
94
95 pub fn graph(&self) -> &G {
97 &self.graph
98 }
99
100 pub fn edge_lengths(&self) -> &[W] {
102 &self.edge_lengths
103 }
104
105 pub fn required_edges(&self) -> &[usize] {
107 &self.required_edges
108 }
109
110 pub fn num_vertices(&self) -> usize {
112 self.graph.num_vertices()
113 }
114
115 pub fn num_edges(&self) -> usize {
117 self.graph.num_edges()
118 }
119
120 pub fn num_required_edges(&self) -> usize {
122 self.required_edges.len()
123 }
124
125 pub fn set_weights(&mut self, weights: Vec<W>) {
127 assert_eq!(weights.len(), self.graph.num_edges());
128 self.edge_lengths = weights;
129 }
130
131 pub fn weights(&self) -> Vec<W> {
133 self.edge_lengths.clone()
134 }
135
136 pub fn is_weighted(&self) -> bool {
138 !W::IS_UNIT
139 }
140
141 pub fn is_valid_solution(&self, config: &[usize]) -> Option<W::Sum> {
146 if config.len() != self.graph.num_edges() {
147 return None;
148 }
149
150 let edges = self.graph.edges();
151 let n = self.graph.num_vertices();
152
153 for &req_idx in &self.required_edges {
155 if config[req_idx] == 0 {
156 return None;
157 }
158 }
159
160 let mut degree = vec![0usize; n];
162 let mut has_edges = false;
163 for (idx, &mult) in config.iter().enumerate() {
164 if mult > 0 {
165 let (u, v) = edges[idx];
166 degree[u] += mult;
167 degree[v] += mult;
168 has_edges = true;
169 }
170 }
171
172 if !has_edges {
174 if self.required_edges.is_empty() {
175 return Some(W::Sum::zero());
176 } else {
177 return None;
178 }
179 }
180
181 for &d in °ree {
183 if d % 2 != 0 {
184 return None;
185 }
186 }
187
188 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
191 let mut first_vertex = None;
192 for (idx, &mult) in config.iter().enumerate() {
193 if mult > 0 {
194 let (u, v) = edges[idx];
195 adj[u].push(v);
196 adj[v].push(u);
197 if first_vertex.is_none() {
198 first_vertex = Some(u);
199 }
200 }
201 }
202
203 let first = match first_vertex {
204 Some(v) => v,
205 None => {
206 if self.required_edges.is_empty() {
207 return Some(W::Sum::zero());
208 } else {
209 return None;
210 }
211 }
212 };
213
214 let mut visited = vec![false; n];
215 let mut queue = VecDeque::new();
216 visited[first] = true;
217 queue.push_back(first);
218
219 while let Some(node) = queue.pop_front() {
220 for &neighbor in &adj[node] {
221 if !visited[neighbor] {
222 visited[neighbor] = true;
223 queue.push_back(neighbor);
224 }
225 }
226 }
227
228 for v in 0..n {
230 if degree[v] > 0 && !visited[v] {
231 return None;
232 }
233 }
234
235 let mut total = W::Sum::zero();
237 for (idx, &mult) in config.iter().enumerate() {
238 for _ in 0..mult {
239 total += self.edge_lengths[idx].to_sum();
240 }
241 }
242
243 Some(total)
244 }
245}
246
247impl<G, W> Problem for RuralPostman<G, W>
248where
249 G: Graph + crate::variant::VariantParam,
250 W: WeightElement + crate::variant::VariantParam,
251{
252 const NAME: &'static str = "RuralPostman";
253 type Value = Min<W::Sum>;
254
255 fn variant() -> Vec<(&'static str, &'static str)> {
256 crate::variant_params![G, W]
257 }
258
259 fn dims(&self) -> Vec<usize> {
260 vec![3; self.graph.num_edges()]
261 }
262
263 fn evaluate(&self, config: &[usize]) -> Min<W::Sum> {
264 Min(self.is_valid_solution(config))
265 }
266}
267
268crate::declare_variants! {
269 default RuralPostman<SimpleGraph, i32> => "2^num_vertices * num_vertices^2",
270}
271
272#[cfg(feature = "example-db")]
273pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
274 use crate::topology::SimpleGraph;
275 let graph = SimpleGraph::new(
278 6,
279 vec![
280 (0, 1),
281 (1, 2),
282 (2, 3),
283 (3, 4),
284 (4, 5),
285 (5, 0),
286 (0, 3),
287 (1, 4),
288 ],
289 );
290 vec![crate::example_db::specs::ModelExampleSpec {
291 id: "rural_postman",
292 instance: Box::new(RuralPostman::new(
293 graph,
294 vec![1, 1, 1, 1, 1, 1, 2, 2],
295 vec![0, 2, 4],
296 )),
297 optimal_config: vec![1, 1, 1, 1, 1, 1, 0, 0],
298 optimal_value: serde_json::json!(6),
299 }]
300}
301
302#[cfg(test)]
303#[path = "../../unit_tests/models/graph/rural_postman.rs"]
304mod tests;