1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{is_simple_st_path, Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::{Min, WeightElement};
11use num_traits::Zero;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "ShortestWeightConstrainedPath",
17 display_name: "Shortest Weight-Constrained Path",
18 aliases: &[],
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 simple s-t path minimizing total length subject to a weight budget",
26 fields: ShortestWeightConstrainedPathCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(try_from = "ShortestWeightConstrainedPathData<G, N>")]
56#[serde(bound(
57 deserialize = "G: Graph + Deserialize<'de>, N: WeightElement + Deserialize<'de>, N::Sum: Deserialize<'de>"
58))]
59pub struct ShortestWeightConstrainedPath<G, N: WeightElement> {
60 graph: G,
62 edge_lengths: Vec<N>,
64 edge_weights: Vec<N>,
66 source_vertex: usize,
68 target_vertex: usize,
70 weight_bound: N::Sum,
72}
73
74#[derive(Deserialize)]
75#[serde(bound(
76 deserialize = "G: Graph + Deserialize<'de>, N: WeightElement + Deserialize<'de>, N::Sum: Deserialize<'de>"
77))]
78struct ShortestWeightConstrainedPathData<G, N: WeightElement> {
79 graph: G,
80 edge_lengths: Vec<N>,
81 edge_weights: Vec<N>,
82 source_vertex: usize,
83 target_vertex: usize,
84 weight_bound: N::Sum,
85}
86
87impl<G, N> TryFrom<ShortestWeightConstrainedPathData<G, N>> for ShortestWeightConstrainedPath<G, N>
88where
89 G: Graph,
90 N: WeightElement,
91{
92 type Error = crate::registry::ConstructionError;
93
94 fn try_from(data: ShortestWeightConstrainedPathData<G, N>) -> Result<Self, Self::Error> {
95 Self::try_new(
96 data.graph,
97 data.edge_lengths,
98 data.edge_weights,
99 data.source_vertex,
100 data.target_vertex,
101 data.weight_bound,
102 )
103 }
104}
105
106#[derive(Debug, Deserialize, crate::CreateSpec)]
107struct ShortestWeightConstrainedPathCreateSpec {
108 graph: SimpleGraph,
110 edge_lengths: Vec<i64>,
112 edge_weights: Vec<i64>,
114 source_vertex: usize,
116 target_vertex: usize,
118 weight_bound: i64,
120}
121
122impl TryFrom<ShortestWeightConstrainedPathCreateSpec>
123 for ShortestWeightConstrainedPath<SimpleGraph, i64>
124{
125 type Error = crate::registry::ConstructionError;
126 fn try_from(spec: ShortestWeightConstrainedPathCreateSpec) -> Result<Self, Self::Error> {
127 Self::try_new(
128 spec.graph,
129 spec.edge_lengths,
130 spec.edge_weights,
131 spec.source_vertex,
132 spec.target_vertex,
133 spec.weight_bound,
134 )
135 }
136}
137
138impl<G: Graph, N: WeightElement> ShortestWeightConstrainedPath<G, N> {
139 fn check_edge_values(
140 graph: &G,
141 values: &[N],
142 label: &str,
143 ) -> Result<(), crate::registry::ConstructionError> {
144 if values.len() != graph.num_edges() {
145 return Err(crate::registry::ConstructionError::length_mismatch(
146 label,
147 values.len(),
148 graph.num_edges(),
149 ));
150 }
151 if !values.iter().all(|value| value.to_sum() > N::Sum::zero()) {
152 return Err(format!("All {label} must be positive (> 0)").into());
153 }
154 Ok(())
155 }
156
157 pub fn new(
164 graph: G,
165 edge_lengths: Vec<N>,
166 edge_weights: Vec<N>,
167 source_vertex: usize,
168 target_vertex: usize,
169 weight_bound: N::Sum,
170 ) -> Self {
171 Self::try_new(
172 graph,
173 edge_lengths,
174 edge_weights,
175 source_vertex,
176 target_vertex,
177 weight_bound,
178 )
179 .unwrap_or_else(|error| panic!("{error}"))
180 }
181
182 fn try_new(
183 graph: G,
184 edge_lengths: Vec<N>,
185 edge_weights: Vec<N>,
186 source_vertex: usize,
187 target_vertex: usize,
188 weight_bound: N::Sum,
189 ) -> Result<Self, crate::registry::ConstructionError> {
190 Self::check_edge_values(&graph, &edge_lengths, "edge lengths")?;
191 Self::check_edge_values(&graph, &edge_weights, "edge weights")?;
192 if source_vertex >= graph.num_vertices() {
193 return Err(format!(
194 "source_vertex {} out of bounds (graph has {} vertices)",
195 source_vertex,
196 graph.num_vertices()
197 )
198 .into());
199 }
200 if target_vertex >= graph.num_vertices() {
201 return Err(format!(
202 "target_vertex {} out of bounds (graph has {} vertices)",
203 target_vertex,
204 graph.num_vertices()
205 )
206 .into());
207 }
208 if weight_bound.partial_cmp(&N::Sum::zero()) != Some(std::cmp::Ordering::Greater) {
209 return Err("weight_bound must be positive (> 0)".into());
210 }
211 Ok(Self {
212 graph,
213 edge_lengths,
214 edge_weights,
215 source_vertex,
216 target_vertex,
217 weight_bound,
218 })
219 }
220
221 pub fn graph(&self) -> &G {
223 &self.graph
224 }
225
226 pub fn edge_lengths(&self) -> &[N] {
228 &self.edge_lengths
229 }
230
231 pub fn edge_weights(&self) -> &[N] {
233 &self.edge_weights
234 }
235
236 pub fn set_lengths(&mut self, edge_lengths: Vec<N>) {
238 Self::check_edge_values(&self.graph, &edge_lengths, "edge lengths")
239 .unwrap_or_else(|error| panic!("{error}"));
240 self.edge_lengths = edge_lengths;
241 }
242
243 pub fn set_weights(&mut self, edge_weights: Vec<N>) {
245 Self::check_edge_values(&self.graph, &edge_weights, "edge weights")
246 .unwrap_or_else(|error| panic!("{error}"));
247 self.edge_weights = edge_weights;
248 }
249
250 pub fn source_vertex(&self) -> usize {
252 self.source_vertex
253 }
254
255 pub fn target_vertex(&self) -> usize {
257 self.target_vertex
258 }
259
260 pub fn weight_bound(&self) -> &N::Sum {
262 &self.weight_bound
263 }
264
265 pub fn is_weighted(&self) -> bool {
267 !N::IS_UNIT
268 }
269
270 pub fn max_weight_bits(&self) -> u64 {
272 crate::types::max_numeric_magnitude_bits(
273 self.edge_weights
274 .iter()
275 .map(|weight| weight.to_sum())
276 .chain(std::iter::once(self.weight_bound.clone())),
277 )
278 }
279
280 pub fn num_vertices(&self) -> usize {
282 self.graph.num_vertices()
283 }
284
285 pub fn num_edges(&self) -> usize {
287 self.graph.num_edges()
288 }
289
290 pub fn is_valid_solution(
295 &self,
296 config: &[bool],
297 ) -> Result<Option<N::Sum>, crate::traits::EvaluationError> {
298 if config.len() != self.graph.num_edges() {
299 return Ok(None);
300 }
301
302 if self.source_vertex == self.target_vertex {
303 if config.contains(&true) {
304 return Ok(None);
305 }
306 return Ok(Some(N::Sum::zero()));
307 }
308
309 let mut total_length = N::Sum::zero();
310 let mut total_weight = N::Sum::zero();
311
312 for (idx, &selected) in config.iter().enumerate() {
313 if !selected {
314 continue;
315 }
316 total_length = N::checked_add_to_sum(
317 total_length,
318 self.edge_lengths[idx].to_sum(),
319 "summing constrained path edge lengths",
320 )?;
321 total_weight = N::checked_add_to_sum(
322 total_weight,
323 self.edge_weights[idx].to_sum(),
324 "summing constrained path edge weights",
325 )?;
326 }
327
328 if total_weight > self.weight_bound.clone() {
329 return Ok(None);
330 }
331 if !is_simple_st_path(
332 self.graph.num_vertices(),
333 &self.graph.edges(),
334 self.source_vertex,
335 self.target_vertex,
336 config,
337 ) {
338 Ok(None)
339 } else {
340 Ok(Some(total_length))
341 }
342 }
343}
344
345impl<G, N> Problem for ShortestWeightConstrainedPath<G, N>
346where
347 G: Graph + crate::variant::VariantParam,
348 N: WeightElement + crate::variant::VariantParam,
349{
350 const NAME: &'static str = "ShortestWeightConstrainedPath";
351 type Solution = Vec<bool>;
352 type Value = Min<N::Sum>;
353
354 crate::problem_parameters![
355 ("max_weight_bits", max_weight_bits),
356 ("num_edges", num_edges),
357 ("num_vertices", num_vertices),
358 ];
359
360 fn variant() -> Vec<(&'static str, &'static str)> {
361 crate::variant_params![G, N]
362 }
363
364 fn evaluate(
365 &self,
366 config: &Self::Solution,
367 ) -> Result<Min<N::Sum>, crate::traits::EvaluationError> {
368 if config.len() != self.graph.num_edges() {
369 return Err(crate::traits::EvaluationError::InvalidConfiguration(
370 "edge-selection length does not match the graph".into(),
371 ));
372 }
373 Ok(Min(self.is_valid_solution(config)?))
374 }
375}
376
377impl<G, N> crate::solvers::BruteForceProblem for ShortestWeightConstrainedPath<G, N>
378where
379 G: Graph + crate::variant::VariantParam,
380 N: WeightElement + crate::variant::VariantParam,
381{
382 fn dimensions(&self) -> Vec<usize> {
383 vec![2; self.graph.num_edges()]
384 }
385}
386
387#[cfg(feature = "example-db")]
388pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
389 vec![crate::example_db::specs::ModelExampleSpec {
390 id: "shortest_weight_constrained_path_simplegraph",
391 instance: Box::new(ShortestWeightConstrainedPath::new(
392 SimpleGraph::new(
393 6,
394 vec![
395 (0, 1),
396 (0, 2),
397 (1, 3),
398 (2, 3),
399 (2, 4),
400 (3, 5),
401 (4, 5),
402 (1, 4),
403 ],
404 ),
405 vec![2, 4, 3, 1, 5, 4, 2, 6],
406 vec![5, 1, 2, 3, 2, 3, 1, 1],
407 0,
408 5,
409 8,
410 )),
411 optimal_config: serde_json::json!(vec![
412 false, true, false, true, false, true, false, false
413 ]),
414 optimal_value: serde_json::json!(9),
415 }]
416}
417
418crate::declare_variants! {
419 default ShortestWeightConstrainedPath<SimpleGraph, i64> => "2^num_edges" create ShortestWeightConstrainedPathCreateSpec,
420}
421
422crate::register_brute_force! {
423 ShortestWeightConstrainedPath<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
424}
425
426#[cfg(test)]
427#[path = "../../unit_tests/models/graph/shortest_weight_constrained_path.rs"]
428mod tests;