problemreductions/models/graph/
eulerian_path.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
19use crate::topology::DirectedGraph;
20use crate::traits::Problem;
21use serde::{Deserialize, Serialize};
22
23inventory::submit! {
24 ProblemSchemaEntry {
25 name: "EulerianPath",
26 display_name: "Eulerian Path",
27 aliases: &[],
28 dimensions: &[],
29 category: crate::registry::ProblemCategory::Graph,
30 module_path: module_path!(),
31 description: "Does the directed multigraph admit a directed trail using every arc exactly once?",
32 fields: &[
33 FieldInfo {
34 name: "graph",
35 type_name: "DirectedGraph",
36 description: "The directed multigraph D=(V,A); parallel arcs and loops allowed",
37 },
38 ],
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct EulerianPath {
74 graph: DirectedGraph,
75}
76
77impl EulerianPath {
78 pub fn new(graph: DirectedGraph) -> Self {
80 Self { graph }
81 }
82
83 pub fn graph(&self) -> &DirectedGraph {
85 &self.graph
86 }
87
88 pub fn num_vertices(&self) -> usize {
90 self.graph.num_vertices()
91 }
92
93 pub fn num_arcs(&self) -> usize {
95 self.graph.num_arcs()
96 }
97
98 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
100 is_valid_eulerian_trail(&self.graph, config)
101 }
102}
103
104impl Problem for EulerianPath {
105 const NAME: &'static str = "EulerianPath";
106 type Solution = Vec<usize>;
107 type Value = crate::types::Or;
108
109 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
110
111 fn variant() -> Vec<(&'static str, &'static str)> {
112 crate::variant_params![]
113 }
114
115 fn evaluate(
116 &self,
117 config: &Self::Solution,
118 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
119 let m = self.graph.num_arcs();
120 if config.len() != m {
121 return Err(crate::traits::EvaluationError::InvalidConfiguration(
122 "arc ordering length does not match the graph".into(),
123 ));
124 }
125 if config.iter().any(|&arc| arc >= m) {
126 return Err(crate::traits::EvaluationError::InvalidConfiguration(
127 "arc ordering contains an out-of-range arc".into(),
128 ));
129 }
130 Ok(crate::types::Or(is_valid_eulerian_trail(
131 &self.graph,
132 config,
133 )))
134 }
135}
136
137impl crate::solvers::BruteForceProblem for EulerianPath {
138 fn dimensions(&self) -> Vec<usize> {
139 let m = self.graph.num_arcs();
140 vec![m; m]
141 }
142}
143
144fn is_valid_eulerian_trail(graph: &DirectedGraph, config: &[usize]) -> bool {
152 let m = graph.num_arcs();
153 if config.len() != m {
154 return false;
155 }
156 if m == 0 {
157 return true;
158 }
159
160 let mut seen = vec![false; m];
162 for &idx in config {
163 if idx >= m || seen[idx] {
164 return false;
165 }
166 seen[idx] = true;
167 }
168
169 let arcs = graph.arcs();
171 for window in config.windows(2) {
172 let (_prev_src, prev_tgt) = arcs[window[0]];
173 let (next_src, _next_tgt) = arcs[window[1]];
174 if prev_tgt != next_src {
175 return false;
176 }
177 }
178 true
179}
180
181#[cfg(feature = "example-db")]
182pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
183 let graph = DirectedGraph::new(3, vec![(0, 1), (0, 1), (1, 2), (2, 0)]);
187 let optimal_config = vec![0usize, 2, 3, 1];
188 vec![crate::example_db::specs::ModelExampleSpec {
189 id: "eulerian_path",
190 instance: Box::new(EulerianPath::new(graph)),
191 optimal_config: serde_json::to_value(optimal_config)
192 .expect("solution serialization must succeed"),
193 optimal_value: serde_json::json!(true),
194 }]
195}
196
197crate::declare_variants! {
198 default EulerianPath => "num_vertices + num_arcs",
199}
200
201crate::register_brute_force! {
202 EulerianPath,
203}
204
205#[cfg(test)]
206#[path = "../../unit_tests/models/graph/eulerian_path.rs"]
207mod tests;