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
68fn build_branch<FV, FA>(
69    add_vertex: &mut FV,
70    add_arc: &mut FA,
71    entry: usize,
72    exit: usize,
73    occurrences: &[ClauseOccurrence],
74    branch_context: &mut BranchContext<'_>,
75) -> BranchBuild
76where
77    FV: FnMut() -> usize,
78    FA: FnMut(usize, usize) -> usize,
79{
80    let mut path_arcs = Vec::with_capacity(2 * occurrences.len() + 3);
81
82    let dummy_odd = add_vertex();
83    let dummy_even = add_vertex();
84    let entry_arc = add_arc(entry, dummy_odd);
85    path_arcs.push(entry_arc);
86    path_arcs.push(add_arc(dummy_odd, dummy_even));
87
88    let mut previous_even = dummy_even;
89
90    for occurrence in occurrences {
91        let odd = add_vertex();
92        let even = add_vertex();
93        path_arcs.push(add_arc(previous_even, odd));
94        let branch_arc = add_arc(odd, even);
95        path_arcs.push(branch_arc);
96
97        let source_arc = add_arc(branch_context.source_2, odd);
98        let clause_arc = add_arc(even, branch_context.clause_vertices[occurrence.clause_idx]);
99        branch_context.clause_routes[occurrence.clause_idx].push(ClauseRoute {
100            variable: occurrence.variable,
101            requires_true: occurrence.requires_true,
102            source_arc,
103            branch_arc,
104            clause_arc,
105        });
106
107        previous_even = even;
108    }
109
110    path_arcs.push(add_arc(previous_even, exit));
111
112    BranchBuild {
113        path_arcs,
114        entry_arc,
115    }
116}
117
118impl Reduction3SATToDirectedTwoCommodityIntegralFlow {
119    #[cfg(any(test, feature = "example-db"))]
120    pub(crate) fn encode_assignment(&self, assignment: &[bool]) -> Vec<usize> {
121        assert_eq!(
122            assignment.len(),
123            self.variable_paths.len(),
124            "assignment length must match num_vars",
125        );
126
127        let num_arcs = self.target.num_arcs();
128        let mut flow = vec![0usize; 2 * num_arcs];
129
130        for &arc_idx in &self.commodity_1_chain_arcs {
131            flow[arc_idx] = 1;
132        }
133
134        for (value, paths) in assignment.iter().zip(&self.variable_paths) {
135            let chosen_path = if *value {
136                &paths.lower_path
137            } else {
138                &paths.upper_path
139            };
140            for &arc_idx in chosen_path {
141                flow[arc_idx] = 1;
142            }
143        }
144
145        for (clause_idx, routes) in self.clause_routes.iter().enumerate() {
146            if let Some(route) = routes
147                .iter()
148                .find(|route| assignment[route.variable] == route.requires_true)
149            {
150                flow[num_arcs + route.source_arc] = 1;
151                flow[num_arcs + route.branch_arc] = 1;
152                flow[num_arcs + route.clause_arc] = 1;
153                flow[num_arcs + self.clause_sink_arcs[clause_idx]] = 1;
154            }
155        }
156
157        flow
158    }
159}
160
161impl ReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {
162    type Source = KSatisfiability<K3>;
163    type Target = DirectedTwoCommodityIntegralFlow;
164
165    fn target_problem(&self) -> &Self::Target {
166        &self.target
167    }
168
169    fn extract_solution(
170        &self,
171        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
172    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
173        crate::rules::traits::validate_target_witness(
174            self.target_problem(),
175            target_solution,
176            |value| value.0,
177            "target witness does not satisfy the target problem",
178        )?;
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#[crate::aggregate_reduction(identity)]
190impl crate::rules::AggregateReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {}
191
192#[reduction(transform = exact {
193    num_vertices = "6 * num_vars + 2 * num_literals + num_clauses + 4",
194    num_arcs = "7 * num_vars + 4 * num_literals + num_clauses + 1",
195    max_capacity = "1",
196})]
197impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
198    type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
199
200    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
201        let source_1 = 0usize;
202        let sink_1 = 1usize;
203        let source_2 = 2usize;
204        let sink_2 = 3usize;
205
206        let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
207        let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
208        for (clause_idx, clause) in self.clauses().iter().enumerate() {
209            for &literal in &clause.literals {
210                let variable = literal_var_index(literal);
211                let occurrence = ClauseOccurrence {
212                    clause_idx,
213                    variable,
214                    requires_true: literal > 0,
215                };
216                if literal > 0 {
217                    positive_occurrences[variable].push(occurrence);
218                } else {
219                    negative_occurrences[variable].push(occurrence);
220                }
221            }
222        }
223
224        let mut next_vertex = 4 + self.num_clauses();
225        let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
226        let mut add_vertex = || {
227            let id = next_vertex;
228            next_vertex += 1;
229            id
230        };
231
232        let mut arcs = Vec::<(usize, usize)>::new();
233        let mut add_arc = |u: usize, v: usize| {
234            arcs.push((u, v));
235            arcs.len() - 1
236        };
237
238        let mut entries = Vec::with_capacity(self.num_vars());
239        let mut exits = Vec::with_capacity(self.num_vars());
240        let mut variable_paths = Vec::with_capacity(self.num_vars());
241        let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
242        let mut branch_context = BranchContext {
243            source_2,
244            clause_vertices: &clause_vertices,
245            clause_routes: &mut clause_routes,
246        };
247
248        for variable in 0..self.num_vars() {
249            let entry = add_vertex();
250            let exit = add_vertex();
251            entries.push(entry);
252            exits.push(exit);
253
254            let upper = build_branch(
255                &mut add_vertex,
256                &mut add_arc,
257                entry,
258                exit,
259                &positive_occurrences[variable],
260                &mut branch_context,
261            );
262            let lower = build_branch(
263                &mut add_vertex,
264                &mut add_arc,
265                entry,
266                exit,
267                &negative_occurrences[variable],
268                &mut branch_context,
269            );
270
271            variable_paths.push(VariablePaths {
272                upper_path: upper.path_arcs,
273                lower_path: lower.path_arcs,
274                lower_entry_arc: lower.entry_arc,
275            });
276        }
277
278        let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
279        if self.num_vars() == 0 {
280            commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
281        } else {
282            commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
283            for variable in 0..self.num_vars() - 1 {
284                commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
285            }
286            commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
287        }
288
289        let clause_sink_arcs: Vec<usize> = clause_vertices
290            .iter()
291            .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
292            .collect();
293
294        let capacities = vec![1i64; arcs.len()];
295        let clause_requirement = i64::try_from(self.num_clauses()).map_err(|_| {
296            crate::rules::ReductionError::integer_overflow::<
297                KSatisfiability<K3>,
298                DirectedTwoCommodityIntegralFlow,
299            >("converting the clause count to an i64 flow requirement")
300        })?;
301        let target = DirectedTwoCommodityIntegralFlow::new(
302            DirectedGraph::new(next_vertex, arcs),
303            capacities,
304            source_1,
305            sink_1,
306            source_2,
307            sink_2,
308            1,
309            clause_requirement,
310        );
311
312        Ok(Reduction3SATToDirectedTwoCommodityIntegralFlow {
313            target,
314            commodity_1_chain_arcs,
315            variable_paths,
316            clause_routes,
317            clause_sink_arcs,
318        })
319    }
320}
321
322#[cfg(feature = "example-db")]
323pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
324    use crate::export::SolutionPair;
325
326    vec![crate::example_db::specs::RuleExampleSpec {
327        id: "ksatisfiability_to_directedtwocommodityintegralflow",
328        build: || {
329            let source = KSatisfiability::<K3>::new(
330                3,
331                vec![
332                    crate::models::formula::CNFClause::new(vec![1, -2, 3]),
333                    crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
334                ],
335            );
336            let reduction =
337                crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source)
338                    .expect("reduction should succeed");
339            let source_config = vec![true, true, false];
340            let target_config = reduction.encode_assignment(&source_config);
341
342            crate::example_db::specs::assemble_rule_example(
343                &source,
344                reduction.target_problem(),
345                vec![SolutionPair {
346                    source_config: serde_json::to_value(source_config)
347                        .expect("solution serialization must succeed"),
348                    target_config: serde_json::to_value(target_config)
349                        .expect("solution serialization must succeed"),
350                }],
351            )
352        },
353    }]
354}
355
356#[cfg(test)]
357#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
358mod tests;