Skip to main content

problemreductions/models/graph/
longest_path.rs

1//! Longest Path problem implementation.
2//!
3//! The Longest Path problem asks for a simple path between two distinguished
4//! vertices that maximizes the total edge length.
5
6use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{is_simple_st_path, Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Max, One, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "LongestPath",
16        display_name: "Longest Path",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20            VariantDimension::new("weight", "i64", &["i64", "One"]),
21        ],
22        category: crate::registry::ProblemCategory::Graph,
23        module_path: module_path!(),
24        description: "Find a simple s-t path of maximum total edge length",
25        fields: LongestPathI64CreateSpec::FIELDS,
26    }
27}
28
29/// The Longest Path problem.
30///
31/// Given a graph `G = (V, E)` with positive edge lengths `l(e)` and
32/// distinguished vertices `s` and `t`, find a simple path from `s` to `t`
33/// maximizing the total length of its selected edges.
34///
35/// # Representation
36///
37/// Each edge is assigned a binary variable:
38/// - `0`: the edge is not selected
39/// - `1`: the edge is selected
40///
41/// A valid configuration must select exactly the edges of one simple
42/// undirected path from `source_vertex` to `target_vertex`.
43#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(try_from = "LongestPathData<G, W>")]
45#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
46pub struct LongestPath<G, W: WeightElement> {
47    graph: G,
48    edge_lengths: Vec<W>,
49    source_vertex: usize,
50    target_vertex: usize,
51}
52
53#[derive(Deserialize)]
54#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
55struct LongestPathData<G, W: WeightElement> {
56    graph: G,
57    edge_lengths: Vec<W>,
58    source_vertex: usize,
59    target_vertex: usize,
60}
61
62impl<G, W> TryFrom<LongestPathData<G, W>> for LongestPath<G, W>
63where
64    G: Graph,
65    W: WeightElement,
66{
67    type Error = crate::registry::ConstructionError;
68
69    fn try_from(data: LongestPathData<G, W>) -> Result<Self, Self::Error> {
70        Self::try_new(
71            data.graph,
72            data.edge_lengths,
73            data.source_vertex,
74            data.target_vertex,
75        )
76    }
77}
78
79macro_rules! longest_path_create_spec {
80    (@lengths $spec:ident, $lengths:ident) => { $spec.$lengths };
81    (@lengths $spec:ident) => { vec![One; $spec.graph.len()] };
82    ($name:ident,$weight:ty $(, $lengths:ident)?) => {
83        #[derive(Debug, Deserialize, crate::CreateSpec)]
84        struct $name {
85            #[create(codec = "edge-list")]
86            graph: Vec<(usize, usize)>,
87            num_vertices: Option<usize>,
88            $(#[create(codec = "comma-separated")]
89            $lengths: Vec<$weight>,)?
90            source_vertex: usize,
91            target_vertex: usize,
92        }
93        impl TryFrom<$name> for LongestPath<SimpleGraph, $weight> {
94            type Error = crate::registry::ConstructionError;
95            fn try_from(spec: $name) -> Result<Self, crate::registry::ConstructionError> {
96                if spec.graph.is_empty() && spec.num_vertices.is_none() {
97                    return Err("num_vertices is required for an empty graph".into());
98                }
99                for &(u, v) in &spec.graph {
100                    if u == v {
101                        return Err("self-loops are not allowed".into());
102                    }
103                }
104                let inferred = spec
105                    .graph
106                    .iter()
107                    .flat_map(|&(u, v)| [u, v])
108                    .max()
109                    .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
110                    .transpose()?
111                    .unwrap_or(0);
112                let count = spec.num_vertices.unwrap_or(inferred);
113                if count < inferred {
114                    return Err("num_vertices is too small".into());
115                }
116                let edge_lengths = longest_path_create_spec!(@lengths spec $(, $lengths)?);
117                Self::try_new(
118                    SimpleGraph::new(count, spec.graph),
119                    edge_lengths,
120                    spec.source_vertex,
121                    spec.target_vertex,
122                )
123            }
124        }
125    };
126}
127longest_path_create_spec!(LongestPathI64CreateSpec, i64, edge_lengths);
128longest_path_create_spec!(LongestPathOneCreateSpec, One);
129
130impl<G: Graph, W: WeightElement> LongestPath<G, W> {
131    /// Create a new LongestPath instance.
132    pub fn new(graph: G, edge_lengths: Vec<W>, source_vertex: usize, target_vertex: usize) -> Self {
133        Self::try_new(graph, edge_lengths, source_vertex, target_vertex)
134            .unwrap_or_else(|error| panic!("{error}"))
135    }
136
137    fn try_new(
138        graph: G,
139        edge_lengths: Vec<W>,
140        source_vertex: usize,
141        target_vertex: usize,
142    ) -> Result<Self, crate::registry::ConstructionError> {
143        Self::check_weights(&graph, &edge_lengths)?;
144        if source_vertex >= graph.num_vertices() {
145            return Err(format!(
146                "source_vertex {} out of bounds (graph has {} vertices)",
147                source_vertex,
148                graph.num_vertices()
149            )
150            .into());
151        }
152        if target_vertex >= graph.num_vertices() {
153            return Err(format!(
154                "target_vertex {} out of bounds (graph has {} vertices)",
155                target_vertex,
156                graph.num_vertices()
157            )
158            .into());
159        }
160        Ok(Self {
161            graph,
162            edge_lengths,
163            source_vertex,
164            target_vertex,
165        })
166    }
167
168    /// Get a reference to the underlying graph.
169    pub fn graph(&self) -> &G {
170        &self.graph
171    }
172
173    /// Get the edge lengths.
174    pub fn edge_lengths(&self) -> &[W] {
175        &self.edge_lengths
176    }
177
178    /// Replace the edge lengths with a new vector.
179    pub fn set_lengths(&mut self, edge_lengths: Vec<W>) {
180        Self::check_weights(&self.graph, &edge_lengths).unwrap_or_else(|error| panic!("{error}"));
181        self.edge_lengths = edge_lengths;
182    }
183
184    fn check_weights(graph: &G, weights: &[W]) -> Result<(), crate::registry::ConstructionError> {
185        if weights.len() != graph.num_edges() {
186            return Err(crate::registry::ConstructionError::length_mismatch(
187                "weights",
188                weights.len(),
189                graph.num_edges(),
190            ));
191        }
192        if !weights
193            .iter()
194            .all(|weight| weight.to_sum() > W::Sum::zero())
195        {
196            return Err("All edge lengths must be positive (> 0)".into());
197        }
198        Ok(())
199    }
200
201    /// Get the source vertex.
202    pub fn source_vertex(&self) -> usize {
203        self.source_vertex
204    }
205
206    /// Get the target vertex.
207    pub fn target_vertex(&self) -> usize {
208        self.target_vertex
209    }
210
211    /// Check whether this problem uses non-unit edge lengths.
212    pub fn is_weighted(&self) -> bool {
213        !W::IS_UNIT
214    }
215
216    /// Get the number of vertices in the graph.
217    pub fn num_vertices(&self) -> usize {
218        self.graph.num_vertices()
219    }
220
221    /// Get the number of edges in the graph.
222    pub fn num_edges(&self) -> usize {
223        self.graph.num_edges()
224    }
225
226    /// Check if a configuration encodes a valid simple source-target path.
227    pub fn is_valid_solution(&self, config: &[bool]) -> bool {
228        is_simple_st_path(
229            self.graph.num_vertices(),
230            &self.graph.edges(),
231            self.source_vertex,
232            self.target_vertex,
233            config,
234        )
235    }
236}
237
238impl<G, W> Problem for LongestPath<G, W>
239where
240    G: Graph + crate::variant::VariantParam,
241    W: WeightElement + crate::variant::VariantParam,
242{
243    const NAME: &'static str = "LongestPath";
244    type Solution = Vec<bool>;
245    type Value = Max<W::Sum>;
246
247    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
248
249    fn variant() -> Vec<(&'static str, &'static str)> {
250        crate::variant_params![G, W]
251    }
252
253    fn evaluate(
254        &self,
255        config: &Self::Solution,
256    ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
257        if config.len() != self.graph.num_edges() {
258            return Err(crate::traits::EvaluationError::InvalidConfiguration(
259                "edge-selection length does not match the graph".into(),
260            ));
261        }
262        Ok({
263            if !self.is_valid_solution(config) {
264                return Ok(Max(None));
265            }
266
267            let mut total = W::Sum::zero();
268            for (idx, &selected) in config.iter().enumerate() {
269                if selected {
270                    total = W::checked_add_to_sum(
271                        total,
272                        self.edge_lengths[idx].to_sum(),
273                        "summing path edge lengths",
274                    )?;
275                }
276            }
277            Max(Some(total))
278        })
279    }
280}
281
282impl<G, W> crate::solvers::BruteForceProblem for LongestPath<G, W>
283where
284    G: Graph + crate::variant::VariantParam,
285    W: WeightElement + crate::variant::VariantParam,
286{
287    fn dimensions(&self) -> Vec<usize> {
288        vec![2; self.graph.num_edges()]
289    }
290}
291
292crate::declare_variants! {
293    default LongestPath<SimpleGraph, i64> => "num_vertices * 2^num_vertices" create LongestPathI64CreateSpec,
294    LongestPath<SimpleGraph, One> => "num_vertices * 2^num_vertices" create LongestPathOneCreateSpec,
295}
296
297crate::register_brute_force! {
298    LongestPath<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
299    LongestPath<SimpleGraph, One> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
300}
301
302#[cfg(feature = "example-db")]
303pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
304    vec![crate::example_db::specs::ModelExampleSpec {
305        id: "longest_path_simplegraph",
306        instance: Box::new(LongestPath::new(
307            SimpleGraph::new(
308                7,
309                vec![
310                    (0, 1),
311                    (0, 2),
312                    (1, 3),
313                    (2, 3),
314                    (2, 4),
315                    (3, 5),
316                    (4, 5),
317                    (4, 6),
318                    (5, 6),
319                    (1, 6),
320                ],
321            ),
322            vec![3, 2, 4, 1, 5, 2, 3, 2, 4, 1],
323            0,
324            6,
325        )),
326        optimal_config: serde_json::json!(vec![
327            true, false, true, true, true, false, true, false, true, false
328        ]),
329        optimal_value: serde_json::json!(20),
330    }]
331}
332
333#[cfg(test)]
334#[path = "../../unit_tests/models/graph/longest_path.rs"]
335mod tests;
336
337crate::decision_problem_meta!(LongestPath<SimpleGraph, One>, "DecisionLongestPath");
338crate::register_decision_variant!(
339    LongestPath<SimpleGraph, One>, "DecisionLongestPath", "num_vertices * 2^num_vertices", &[],
340    "Does a feasible solution have objective value >= the bound?",
341    category: crate::registry::ProblemCategory::Graph,
342    dims: [
343            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
344            VariantDimension::new("weight", "One", &["One"]),
345        ],
346    fields: [
347        crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Graph edges as comma-separated vertex pairs." },
348        crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices, including isolated vertices." },
349        crate::registry::FieldInfo { name: "source_vertex", type_name: "usize", description: "Start vertex of the path." },
350        crate::registry::FieldInfo { name: "target_vertex", type_name: "usize", description: "End vertex of the path." },
351        crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values >= this bound" },
352    ],
353    decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
354);
355
356#[cfg(feature = "example-db")]
357pub(crate) fn decision_canonical_rule_example_specs(
358) -> Vec<crate::example_db::specs::RuleExampleSpec> {
359    vec![crate::example_db::specs::RuleExampleSpec {
360        id: "decision_longest_path_to_longest_path",
361        build: || {
362            let source = crate::models::decision::Decision::new(
363                LongestPath::new(SimpleGraph::path(3), vec![crate::types::One; 2], 0, 2),
364                2,
365            );
366            let witness = serde_json::json!(vec![true, true]);
367            crate::example_db::specs::rule_example_with_witness::<_, LongestPath<SimpleGraph, One>>(
368                source,
369                crate::export::SolutionPair {
370                    source_config: witness.clone(),
371                    target_config: witness,
372                },
373            )
374        },
375    }]
376}