1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::graph::LongestPath;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::topology::{Graph, SimpleGraph};
13
14#[derive(Debug, Clone)]
15pub struct ReductionLongestPathToILP {
16 target: ILP<i32>,
17 num_edges: usize,
18}
19
20impl ReductionLongestPathToILP {
21 fn arc_var(edge_idx: usize, dir: usize) -> usize {
22 2 * edge_idx + dir
23 }
24}
25
26impl ReductionResult for ReductionLongestPathToILP {
27 type Source = LongestPath<SimpleGraph, i32>;
28 type Target = ILP<i32>;
29
30 fn target_problem(&self) -> &ILP<i32> {
31 &self.target
32 }
33
34 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
35 (0..self.num_edges)
36 .map(|edge_idx| {
37 usize::from(
38 target_solution
39 .get(Self::arc_var(edge_idx, 0))
40 .copied()
41 .unwrap_or(0)
42 > 0
43 || target_solution
44 .get(Self::arc_var(edge_idx, 1))
45 .copied()
46 .unwrap_or(0)
47 > 0,
48 )
49 })
50 .collect()
51 }
52}
53
54#[reduction(overhead = {
55 num_vars = "2 * num_edges + num_vertices",
56 num_constraints = "5 * num_edges + 4 * num_vertices + 1",
57})]
58impl ReduceTo<ILP<i32>> for LongestPath<SimpleGraph, i32> {
59 type Result = ReductionLongestPathToILP;
60
61 fn reduce_to(&self) -> Self::Result {
62 let edges = self.graph().edges();
63 let num_vertices = self.num_vertices();
64 let num_edges = self.num_edges();
65 let num_vars = 2 * num_edges + num_vertices;
66 let source = self.source_vertex();
67 let target = self.target_vertex();
68 let big_m = num_vertices as f64;
69
70 let order_var = |vertex: usize| 2 * num_edges + vertex;
71
72 let mut outgoing = vec![Vec::new(); num_vertices];
73 let mut incoming = vec![Vec::new(); num_vertices];
74
75 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
76 let forward = ReductionLongestPathToILP::arc_var(edge_idx, 0);
77 let reverse = ReductionLongestPathToILP::arc_var(edge_idx, 1);
78 outgoing[u].push((forward, 1.0));
79 incoming[v].push((forward, 1.0));
80 outgoing[v].push((reverse, 1.0));
81 incoming[u].push((reverse, 1.0));
82 }
83
84 let mut constraints = Vec::new();
85
86 for edge_idx in 0..num_edges {
88 constraints.push(LinearConstraint::le(
89 vec![(ReductionLongestPathToILP::arc_var(edge_idx, 0), 1.0)],
90 1.0,
91 ));
92 constraints.push(LinearConstraint::le(
93 vec![(ReductionLongestPathToILP::arc_var(edge_idx, 1), 1.0)],
94 1.0,
95 ));
96 }
97
98 for vertex in 0..num_vertices {
100 constraints.push(LinearConstraint::le(
101 vec![(order_var(vertex), 1.0)],
102 num_vertices.saturating_sub(1) as f64,
103 ));
104 }
105
106 for vertex in 0..num_vertices {
108 let mut balance_terms = outgoing[vertex].clone();
109 for &(var, coef) in &incoming[vertex] {
110 balance_terms.push((var, -coef));
111 }
112
113 let rhs = if source != target {
114 if vertex == source {
115 1.0
116 } else if vertex == target {
117 -1.0
118 } else {
119 0.0
120 }
121 } else {
122 0.0
123 };
124 constraints.push(LinearConstraint::eq(balance_terms, rhs));
125 constraints.push(LinearConstraint::le(outgoing[vertex].clone(), 1.0));
126 constraints.push(LinearConstraint::le(incoming[vertex].clone(), 1.0));
127 }
128
129 for edge_idx in 0..num_edges {
131 constraints.push(LinearConstraint::le(
132 vec![
133 (ReductionLongestPathToILP::arc_var(edge_idx, 0), 1.0),
134 (ReductionLongestPathToILP::arc_var(edge_idx, 1), 1.0),
135 ],
136 1.0,
137 ));
138 }
139
140 for (edge_idx, &(u, v)) in edges.iter().enumerate() {
142 constraints.push(LinearConstraint::ge(
143 vec![
144 (order_var(v), 1.0),
145 (order_var(u), -1.0),
146 (ReductionLongestPathToILP::arc_var(edge_idx, 0), -big_m),
147 ],
148 1.0 - big_m,
149 ));
150 constraints.push(LinearConstraint::ge(
151 vec![
152 (order_var(u), 1.0),
153 (order_var(v), -1.0),
154 (ReductionLongestPathToILP::arc_var(edge_idx, 1), -big_m),
155 ],
156 1.0 - big_m,
157 ));
158 }
159
160 constraints.push(LinearConstraint::eq(vec![(order_var(source), 1.0)], 0.0));
161
162 let mut objective = Vec::with_capacity(2 * num_edges);
163 for (edge_idx, length) in self.edge_lengths().iter().enumerate() {
164 let coeff = f64::from(*length);
165 objective.push((ReductionLongestPathToILP::arc_var(edge_idx, 0), coeff));
166 objective.push((ReductionLongestPathToILP::arc_var(edge_idx, 1), coeff));
167 }
168
169 ReductionLongestPathToILP {
170 target: ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize),
171 num_edges,
172 }
173 }
174}
175
176#[cfg(feature = "example-db")]
177pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
178 vec![crate::example_db::specs::RuleExampleSpec {
179 id: "longestpath_to_ilp",
180 build: || {
181 let source =
182 LongestPath::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![2, 3], 0, 2);
183 crate::example_db::specs::rule_example_via_ilp::<_, i32>(source)
184 },
185 }]
186}
187
188#[cfg(test)]
189#[path = "../unit_tests/rules/longestpath_ilp.rs"]
190mod tests;