Skip to main content

problemreductions/rules/
ksatisfiability_directedtwocommodityintegralflow.rs

1//! Reduction from KSatisfiability (3-SAT) to DirectedTwoCommodityIntegralFlow.
2//!
3//! This uses a padded occurrence-lobe variant of the Even-Itai-Shamir
4//! construction: each variable branch begins with one dummy segment, then one
5//! segment per literal occurrence of that polarity. Commodity 1 chooses exactly
6//! one branch per variable. Commodity 2 must enter a literal-occurrence segment
7//! from `s_2`, traverse that segment's internal arc, and then exit to a clause
8//! vertex before reaching `t_2`.
9
10use crate::models::formula::KSatisfiability;
11use crate::models::graph::DirectedTwoCommodityIntegralFlow;
12use crate::reduction;
13use crate::rules::traits::{ReduceTo, ReductionResult};
14use crate::topology::DirectedGraph;
15use crate::variant::K3;
16
17#[derive(Debug, Clone, Copy)]
18struct ClauseOccurrence {
19    clause_idx: usize,
20    variable: usize,
21    requires_true: bool,
22}
23
24#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
25#[derive(Debug, Clone)]
26struct VariablePaths {
27    upper_path: Vec<usize>,
28    lower_path: Vec<usize>,
29    lower_entry_arc: usize,
30}
31
32#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
33#[derive(Debug, Clone)]
34struct ClauseRoute {
35    variable: usize,
36    requires_true: bool,
37    source_arc: usize,
38    branch_arc: usize,
39    clause_arc: usize,
40}
41
42#[derive(Debug, Clone)]
43struct BranchBuild {
44    path_arcs: Vec<usize>,
45    entry_arc: usize,
46}
47
48struct BranchContext<'a> {
49    source_2: usize,
50    clause_vertices: &'a [usize],
51    clause_routes: &'a mut [Vec<ClauseRoute>],
52}
53
54#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
55#[derive(Debug, Clone)]
56pub struct Reduction3SATToDirectedTwoCommodityIntegralFlow {
57    target: DirectedTwoCommodityIntegralFlow,
58    commodity_1_chain_arcs: Vec<usize>,
59    variable_paths: Vec<VariablePaths>,
60    clause_routes: Vec<Vec<ClauseRoute>>,
61    clause_sink_arcs: Vec<usize>,
62}
63
64fn literal_var_index(literal: i64) -> usize {
65    literal.unsigned_abs() as usize - 1
66}
67
68#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
69fn literal_satisfied(requires_true: bool, assignment: &[bool], variable: usize) -> bool {
70    assignment.get(variable).copied().unwrap_or(false) == requires_true
71}
72
73fn build_branch<FV, FA>(
74    add_vertex: &mut FV,
75    add_arc: &mut FA,
76    entry: usize,
77    exit: usize,
78    occurrences: &[ClauseOccurrence],
79    branch_context: &mut BranchContext<'_>,
80) -> BranchBuild
81where
82    FV: FnMut() -> usize,
83    FA: FnMut(usize, usize) -> usize,
84{
85    let mut path_arcs = Vec::with_capacity(2 * occurrences.len() + 3);
86
87    let dummy_odd = add_vertex();
88    let dummy_even = add_vertex();
89    let entry_arc = add_arc(entry, dummy_odd);
90    path_arcs.push(entry_arc);
91    path_arcs.push(add_arc(dummy_odd, dummy_even));
92
93    let mut previous_even = dummy_even;
94
95    for occurrence in occurrences {
96        let odd = add_vertex();
97        let even = add_vertex();
98        path_arcs.push(add_arc(previous_even, odd));
99        let branch_arc = add_arc(odd, even);
100        path_arcs.push(branch_arc);
101
102        let source_arc = add_arc(branch_context.source_2, odd);
103        let clause_arc = add_arc(even, branch_context.clause_vertices[occurrence.clause_idx]);
104        branch_context.clause_routes[occurrence.clause_idx].push(ClauseRoute {
105            variable: occurrence.variable,
106            requires_true: occurrence.requires_true,
107            source_arc,
108            branch_arc,
109            clause_arc,
110        });
111
112        previous_even = even;
113    }
114
115    path_arcs.push(add_arc(previous_even, exit));
116
117    BranchBuild {
118        path_arcs,
119        entry_arc,
120    }
121}
122
123impl Reduction3SATToDirectedTwoCommodityIntegralFlow {
124    #[cfg(any(test, feature = "example-db"))]
125    pub(crate) fn encode_assignment(&self, assignment: &[bool]) -> Vec<usize> {
126        assert_eq!(
127            assignment.len(),
128            self.variable_paths.len(),
129            "assignment length must match num_vars",
130        );
131
132        let num_arcs = self.target.num_arcs();
133        let mut flow = vec![0usize; 2 * num_arcs];
134
135        for &arc_idx in &self.commodity_1_chain_arcs {
136            flow[arc_idx] = 1;
137        }
138
139        for (value, paths) in assignment.iter().zip(&self.variable_paths) {
140            let chosen_path = if *value {
141                &paths.lower_path
142            } else {
143                &paths.upper_path
144            };
145            for &arc_idx in chosen_path {
146                flow[arc_idx] = 1;
147            }
148        }
149
150        for (clause_idx, routes) in self.clause_routes.iter().enumerate() {
151            if let Some(route) = routes
152                .iter()
153                .find(|route| literal_satisfied(route.requires_true, assignment, route.variable))
154            {
155                flow[num_arcs + route.source_arc] = 1;
156                flow[num_arcs + route.branch_arc] = 1;
157                flow[num_arcs + route.clause_arc] = 1;
158                flow[num_arcs + self.clause_sink_arcs[clause_idx]] = 1;
159            }
160        }
161
162        flow
163    }
164}
165
166impl ReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {
167    type Source = KSatisfiability<K3>;
168    type Target = DirectedTwoCommodityIntegralFlow;
169
170    fn target_problem(&self) -> &Self::Target {
171        &self.target
172    }
173
174    fn extract_solution(
175        &self,
176        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
177    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
178        crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
179
180        Ok({
181            self.variable_paths
182                .iter()
183                .map(|paths| target_solution[paths.lower_entry_arc] > 0)
184                .collect()
185        })
186    }
187}
188
189#[reduction(
190    transform = exact {
191        num_vertices = "6 * num_vars + 2 * num_literals + num_clauses + 4",
192        num_arcs = "7 * num_vars + 4 * num_literals + num_clauses + 1",
193    },
194    unavailable = {
195        max_capacity = "the exact target parameter is not represented by this reduction's symbolic transform",
196    }
197)]
198impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
199    type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
200
201    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
202        let source_1 = 0usize;
203        let sink_1 = 1usize;
204        let source_2 = 2usize;
205        let sink_2 = 3usize;
206
207        let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
208        let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
209        for (clause_idx, clause) in self.clauses().iter().enumerate() {
210            for &literal in &clause.literals {
211                let variable = literal_var_index(literal);
212                let occurrence = ClauseOccurrence {
213                    clause_idx,
214                    variable,
215                    requires_true: literal > 0,
216                };
217                if literal > 0 {
218                    positive_occurrences[variable].push(occurrence);
219                } else {
220                    negative_occurrences[variable].push(occurrence);
221                }
222            }
223        }
224
225        let mut next_vertex = 4 + self.num_clauses();
226        let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
227        let mut add_vertex = || {
228            let id = next_vertex;
229            next_vertex += 1;
230            id
231        };
232
233        let mut arcs = Vec::<(usize, usize)>::new();
234        let mut add_arc = |u: usize, v: usize| {
235            arcs.push((u, v));
236            arcs.len() - 1
237        };
238
239        let mut entries = Vec::with_capacity(self.num_vars());
240        let mut exits = Vec::with_capacity(self.num_vars());
241        let mut variable_paths = Vec::with_capacity(self.num_vars());
242        let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
243        let mut branch_context = BranchContext {
244            source_2,
245            clause_vertices: &clause_vertices,
246            clause_routes: &mut clause_routes,
247        };
248
249        for variable in 0..self.num_vars() {
250            let entry = add_vertex();
251            let exit = add_vertex();
252            entries.push(entry);
253            exits.push(exit);
254
255            let upper = build_branch(
256                &mut add_vertex,
257                &mut add_arc,
258                entry,
259                exit,
260                &positive_occurrences[variable],
261                &mut branch_context,
262            );
263            let lower = build_branch(
264                &mut add_vertex,
265                &mut add_arc,
266                entry,
267                exit,
268                &negative_occurrences[variable],
269                &mut branch_context,
270            );
271
272            variable_paths.push(VariablePaths {
273                upper_path: upper.path_arcs,
274                lower_path: lower.path_arcs,
275                lower_entry_arc: lower.entry_arc,
276            });
277        }
278
279        let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
280        if self.num_vars() == 0 {
281            commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
282        } else {
283            commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
284            for variable in 0..self.num_vars() - 1 {
285                commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
286            }
287            commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
288        }
289
290        let clause_sink_arcs: Vec<usize> = clause_vertices
291            .iter()
292            .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
293            .collect();
294
295        let capacities = vec![1i64; arcs.len()];
296        let clause_requirement = i64::try_from(self.num_clauses()).map_err(|_| {
297            crate::rules::ReductionError::integer_overflow::<
298                KSatisfiability<K3>,
299                DirectedTwoCommodityIntegralFlow,
300            >("converting the clause count to an i64 flow requirement")
301        })?;
302        let target = DirectedTwoCommodityIntegralFlow::new(
303            DirectedGraph::new(next_vertex, arcs),
304            capacities,
305            source_1,
306            sink_1,
307            source_2,
308            sink_2,
309            1,
310            clause_requirement,
311        );
312
313        Ok(Reduction3SATToDirectedTwoCommodityIntegralFlow {
314            target,
315            commodity_1_chain_arcs,
316            variable_paths,
317            clause_routes,
318            clause_sink_arcs,
319        })
320    }
321}
322
323#[cfg(feature = "example-db")]
324pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
325    use crate::export::SolutionPair;
326
327    vec![crate::example_db::specs::RuleExampleSpec {
328        id: "ksatisfiability_to_directedtwocommodityintegralflow",
329        build: || {
330            let source = KSatisfiability::<K3>::new(
331                3,
332                vec![
333                    crate::models::formula::CNFClause::new(vec![1, -2, 3]),
334                    crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
335                ],
336            );
337            let reduction =
338                crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source)
339                    .expect("reduction should succeed");
340            let source_config = vec![true, true, false];
341            let target_config = reduction.encode_assignment(&source_config);
342
343            crate::example_db::specs::assemble_rule_example(
344                &source,
345                reduction.target_problem(),
346                vec![SolutionPair {
347                    source_config: serde_json::to_value(source_config)
348                        .expect("solution serialization must succeed"),
349                    target_config: serde_json::to_value(target_config)
350                        .expect("solution serialization must succeed"),
351                }],
352            )
353        },
354    }]
355}
356
357#[cfg(test)]
358#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
359mod tests;