problemreductions/models/graph/
path_constrained_network_flow.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::topology::DirectedGraph;
11use crate::traits::Problem;
12use serde::{Deserialize, Serialize};
13use std::collections::HashSet;
14
15inventory::submit! {
16 ProblemSchemaEntry {
17 name: "PathConstrainedNetworkFlow",
18 display_name: "Path-Constrained Network Flow",
19 aliases: &[],
20 dimensions: &[],
21 module_path: module_path!(),
22 description: "Integral flow feasibility on a prescribed collection of directed s-t paths",
23 fields: &[
24 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G = (V, A)" },
25 FieldInfo { name: "capacities", type_name: "Vec<u64>", description: "Capacity c(a) for each arc" },
26 FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
27 FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
28 FieldInfo { name: "paths", type_name: "Vec<Vec<usize>>", description: "Prescribed directed s-t paths as arc-index sequences" },
29 FieldInfo { name: "requirement", type_name: "u64", description: "Required total flow R" },
30 ],
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct PathConstrainedNetworkFlow {
44 graph: DirectedGraph,
45 capacities: Vec<u64>,
46 source: usize,
47 sink: usize,
48 paths: Vec<Vec<usize>>,
49 requirement: u64,
50}
51
52impl PathConstrainedNetworkFlow {
53 pub fn new(
62 graph: DirectedGraph,
63 capacities: Vec<u64>,
64 source: usize,
65 sink: usize,
66 paths: Vec<Vec<usize>>,
67 requirement: u64,
68 ) -> Self {
69 let num_vertices = graph.num_vertices();
70 assert_eq!(
71 capacities.len(),
72 graph.num_arcs(),
73 "capacities length must match graph num_arcs"
74 );
75 assert!(
76 source < num_vertices,
77 "source ({source}) >= num_vertices ({num_vertices})"
78 );
79 assert!(
80 sink < num_vertices,
81 "sink ({sink}) >= num_vertices ({num_vertices})"
82 );
83 assert_ne!(source, sink, "source and sink must be distinct");
84
85 for path in &paths {
86 Self::assert_valid_path(&graph, path, source, sink);
87 }
88
89 Self {
90 graph,
91 capacities,
92 source,
93 sink,
94 paths,
95 requirement,
96 }
97 }
98
99 fn assert_valid_path(graph: &DirectedGraph, path: &[usize], source: usize, sink: usize) {
100 assert!(!path.is_empty(), "prescribed paths must be non-empty");
101
102 let arcs = graph.arcs();
103 let mut visited_vertices = HashSet::from([source]);
104 let mut current = source;
105
106 for &arc_idx in path {
107 let &(tail, head) = arcs
108 .get(arc_idx)
109 .unwrap_or_else(|| panic!("path arc index {arc_idx} out of bounds"));
110 assert_eq!(
111 tail, current,
112 "prescribed path is not contiguous: expected arc leaving vertex {current}, got {tail}->{head}"
113 );
114 assert!(
115 visited_vertices.insert(head),
116 "prescribed path repeats vertex {head}, so it is not a simple path"
117 );
118 current = head;
119 }
120
121 assert_eq!(
122 current, sink,
123 "prescribed path must end at sink {sink}, ended at {current}"
124 );
125 }
126
127 fn path_bottleneck(&self, path: &[usize]) -> u64 {
128 path.iter()
129 .map(|&arc_idx| self.capacities[arc_idx])
130 .min()
131 .unwrap_or(0)
132 }
133
134 pub fn graph(&self) -> &DirectedGraph {
136 &self.graph
137 }
138
139 pub fn capacities(&self) -> &[u64] {
141 &self.capacities
142 }
143
144 pub fn paths(&self) -> &[Vec<usize>] {
146 &self.paths
147 }
148
149 pub fn source(&self) -> usize {
151 self.source
152 }
153
154 pub fn sink(&self) -> usize {
156 self.sink
157 }
158
159 pub fn requirement(&self) -> u64 {
161 self.requirement
162 }
163
164 pub fn set_requirement(&mut self, requirement: u64) {
166 self.requirement = requirement;
167 }
168
169 pub fn num_vertices(&self) -> usize {
171 self.graph.num_vertices()
172 }
173
174 pub fn num_arcs(&self) -> usize {
176 self.graph.num_arcs()
177 }
178
179 pub fn num_paths(&self) -> usize {
181 self.paths.len()
182 }
183
184 pub fn max_capacity(&self) -> u64 {
186 self.capacities.iter().copied().max().unwrap_or(0)
187 }
188
189 pub fn is_feasible(&self, config: &[usize]) -> bool {
191 if config.len() != self.paths.len() {
192 return false;
193 }
194
195 let mut arc_loads = vec![0_u64; self.capacities.len()];
196 let mut total_flow = 0_u64;
197
198 for (flow_value, path) in config.iter().copied().zip(&self.paths) {
199 let path_flow = flow_value as u64;
200 if path_flow > self.path_bottleneck(path) {
201 return false;
202 }
203
204 total_flow += path_flow;
205 for &arc_idx in path {
206 arc_loads[arc_idx] += path_flow;
207 if arc_loads[arc_idx] > self.capacities[arc_idx] {
208 return false;
209 }
210 }
211 }
212
213 total_flow >= self.requirement
214 }
215}
216
217impl Problem for PathConstrainedNetworkFlow {
218 const NAME: &'static str = "PathConstrainedNetworkFlow";
219 type Value = crate::types::Or;
220
221 fn dims(&self) -> Vec<usize> {
222 self.paths
223 .iter()
224 .map(|path| (self.path_bottleneck(path) as usize) + 1)
225 .collect()
226 }
227
228 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
229 crate::types::Or(self.is_feasible(config))
230 }
231
232 fn variant() -> Vec<(&'static str, &'static str)> {
233 crate::variant_params![]
234 }
235}
236
237crate::declare_variants! {
238 default PathConstrainedNetworkFlow => "(max_capacity + 1)^num_paths",
239}
240
241#[cfg(feature = "example-db")]
242pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
243 vec![crate::example_db::specs::ModelExampleSpec {
244 id: "path_constrained_network_flow",
245 instance: Box::new(PathConstrainedNetworkFlow::new(
246 DirectedGraph::new(
247 8,
248 vec![
249 (0, 1),
250 (0, 2),
251 (1, 3),
252 (1, 4),
253 (2, 4),
254 (3, 5),
255 (4, 5),
256 (4, 6),
257 (5, 7),
258 (6, 7),
259 ],
260 ),
261 vec![2, 1, 1, 1, 1, 1, 1, 1, 2, 1],
262 0,
263 7,
264 vec![
265 vec![0, 2, 5, 8],
266 vec![0, 3, 6, 8],
267 vec![0, 3, 7, 9],
268 vec![1, 4, 6, 8],
269 vec![1, 4, 7, 9],
270 ],
271 3,
272 )),
273 optimal_config: vec![1, 1, 0, 0, 1],
274 optimal_value: serde_json::json!(true),
275 }]
276}
277
278#[cfg(test)]
279#[path = "../../unit_tests/models/graph/path_constrained_network_flow.rs"]
280mod tests;