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: i32) -> 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: &[usize], variable: usize) -> bool {
70    assignment.get(variable).copied().unwrap_or(0) == usize::from(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: &[usize]) -> 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 == 1 {
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(&self, target_solution: &[usize]) -> Vec<usize> {
175        self.variable_paths
176            .iter()
177            .map(|paths| {
178                usize::from(
179                    target_solution
180                        .get(paths.lower_entry_arc)
181                        .copied()
182                        .unwrap_or(0)
183                        > 0,
184                )
185            })
186            .collect()
187    }
188}
189
190#[reduction(overhead = {
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})]
194impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
195    type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
196
197    fn reduce_to(&self) -> Self::Result {
198        let source_1 = 0usize;
199        let sink_1 = 1usize;
200        let source_2 = 2usize;
201        let sink_2 = 3usize;
202
203        let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
204        let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
205        for (clause_idx, clause) in self.clauses().iter().enumerate() {
206            for &literal in &clause.literals {
207                let variable = literal_var_index(literal);
208                let occurrence = ClauseOccurrence {
209                    clause_idx,
210                    variable,
211                    requires_true: literal > 0,
212                };
213                if literal > 0 {
214                    positive_occurrences[variable].push(occurrence);
215                } else {
216                    negative_occurrences[variable].push(occurrence);
217                }
218            }
219        }
220
221        let mut next_vertex = 4 + self.num_clauses();
222        let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
223        let mut add_vertex = || {
224            let id = next_vertex;
225            next_vertex += 1;
226            id
227        };
228
229        let mut arcs = Vec::<(usize, usize)>::new();
230        let mut add_arc = |u: usize, v: usize| {
231            arcs.push((u, v));
232            arcs.len() - 1
233        };
234
235        let mut entries = Vec::with_capacity(self.num_vars());
236        let mut exits = Vec::with_capacity(self.num_vars());
237        let mut variable_paths = Vec::with_capacity(self.num_vars());
238        let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
239        let mut branch_context = BranchContext {
240            source_2,
241            clause_vertices: &clause_vertices,
242            clause_routes: &mut clause_routes,
243        };
244
245        for variable in 0..self.num_vars() {
246            let entry = add_vertex();
247            let exit = add_vertex();
248            entries.push(entry);
249            exits.push(exit);
250
251            let upper = build_branch(
252                &mut add_vertex,
253                &mut add_arc,
254                entry,
255                exit,
256                &positive_occurrences[variable],
257                &mut branch_context,
258            );
259            let lower = build_branch(
260                &mut add_vertex,
261                &mut add_arc,
262                entry,
263                exit,
264                &negative_occurrences[variable],
265                &mut branch_context,
266            );
267
268            variable_paths.push(VariablePaths {
269                upper_path: upper.path_arcs,
270                lower_path: lower.path_arcs,
271                lower_entry_arc: lower.entry_arc,
272            });
273        }
274
275        let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
276        if self.num_vars() == 0 {
277            commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
278        } else {
279            commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
280            for variable in 0..self.num_vars() - 1 {
281                commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
282            }
283            commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
284        }
285
286        let clause_sink_arcs: Vec<usize> = clause_vertices
287            .iter()
288            .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
289            .collect();
290
291        let capacities = vec![1u64; arcs.len()];
292        let target = DirectedTwoCommodityIntegralFlow::new(
293            DirectedGraph::new(next_vertex, arcs),
294            capacities,
295            source_1,
296            sink_1,
297            source_2,
298            sink_2,
299            1,
300            self.num_clauses() as u64,
301        );
302
303        Reduction3SATToDirectedTwoCommodityIntegralFlow {
304            target,
305            commodity_1_chain_arcs,
306            variable_paths,
307            clause_routes,
308            clause_sink_arcs,
309        }
310    }
311}
312
313#[cfg(feature = "example-db")]
314pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
315    use crate::export::SolutionPair;
316
317    vec![crate::example_db::specs::RuleExampleSpec {
318        id: "ksatisfiability_to_directedtwocommodityintegralflow",
319        build: || {
320            let source = KSatisfiability::<K3>::new(
321                3,
322                vec![
323                    crate::models::formula::CNFClause::new(vec![1, -2, 3]),
324                    crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
325                ],
326            );
327            let reduction =
328                crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source);
329            let source_config = vec![1, 1, 0];
330            let target_config = reduction.encode_assignment(&source_config);
331
332            crate::example_db::specs::assemble_rule_example(
333                &source,
334                reduction.target_problem(),
335                vec![SolutionPair {
336                    source_config,
337                    target_config,
338                }],
339            )
340        },
341    }]
342}
343
344#[cfg(test)]
345#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
346mod tests;