Skip to main content

problemreductions/rules/
satisfiability_integralflowhomologousarcs.rs

1//! Reduction from Satisfiability to IntegralFlowHomologousArcs.
2//!
3//! Follows the clause-stage flow construction described by Sahni (1974):
4//! one unit of flow per variable chooses either the T or F channel, and each
5//! clause stage routes the channels corresponding to literals of the negated
6//! clause through a shared bottleneck. Homologous entry/exit pairs force each
7//! variable's bottleneck flow to stay on its own channel.
8
9use crate::models::formula::Satisfiability;
10use crate::models::graph::IntegralFlowHomologousArcs;
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13use crate::topology::DirectedGraph;
14
15#[derive(Debug, Clone)]
16struct VariablePaths {
17    true_path: Vec<usize>,
18    false_path: Vec<usize>,
19    true_base_arc: usize,
20}
21
22#[derive(Debug, Clone, Copy)]
23struct NodeIndexer {
24    num_vars: usize,
25    num_clauses: usize,
26}
27
28impl NodeIndexer {
29    fn source(self) -> usize {
30        0
31    }
32
33    fn sink(self) -> usize {
34        1
35    }
36
37    fn split(self, variable: usize) -> usize {
38        2 + variable
39    }
40
41    fn boundary_base(self) -> usize {
42        2 + self.num_vars
43    }
44
45    fn channel(self, boundary: usize, variable: usize, is_true: bool) -> usize {
46        self.boundary_base() + (boundary * self.num_vars + variable) * 2 + usize::from(!is_true)
47    }
48
49    fn clause_base(self) -> usize {
50        self.boundary_base() + (self.num_clauses + 1) * self.num_vars * 2
51    }
52
53    fn collector(self, clause: usize) -> usize {
54        self.clause_base() + clause * 2
55    }
56
57    fn distributor(self, clause: usize) -> usize {
58        self.collector(clause) + 1
59    }
60
61    fn total_vertices(self) -> usize {
62        self.clause_base() + self.num_clauses * 2
63    }
64}
65
66/// Result of reducing SAT to integral flow with homologous arcs.
67#[derive(Debug, Clone)]
68pub struct ReductionSATToIntegralFlowHomologousArcs {
69    target: IntegralFlowHomologousArcs,
70    variable_paths: Vec<VariablePaths>,
71}
72
73impl ReductionSATToIntegralFlowHomologousArcs {
74    #[cfg(any(test, feature = "example-db"))]
75    fn encode_assignment(&self, assignment: &[usize]) -> Vec<usize> {
76        assert_eq!(
77            assignment.len(),
78            self.variable_paths.len(),
79            "assignment length must match num_vars",
80        );
81
82        let mut flow = vec![0usize; self.target.num_arcs()];
83        for (value, paths) in assignment.iter().zip(&self.variable_paths) {
84            let path = if *value == 0 {
85                &paths.false_path
86            } else {
87                &paths.true_path
88            };
89            for &arc_idx in path {
90                flow[arc_idx] += 1;
91            }
92        }
93        flow
94    }
95}
96
97impl ReductionResult for ReductionSATToIntegralFlowHomologousArcs {
98    type Source = Satisfiability;
99    type Target = IntegralFlowHomologousArcs;
100
101    fn target_problem(&self) -> &Self::Target {
102        &self.target
103    }
104
105    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
106        self.variable_paths
107            .iter()
108            .map(|paths| {
109                usize::from(
110                    target_solution
111                        .get(paths.true_base_arc)
112                        .copied()
113                        .unwrap_or(0)
114                        > 0,
115                )
116            })
117            .collect()
118    }
119}
120
121#[reduction(overhead = {
122    num_vertices = "2 * num_vars * num_clauses + 3 * num_vars + 2 * num_clauses + 2",
123    num_arcs = "2 * num_vars * num_clauses + 5 * num_vars + num_clauses + num_literals",
124})]
125impl ReduceTo<IntegralFlowHomologousArcs> for Satisfiability {
126    type Result = ReductionSATToIntegralFlowHomologousArcs;
127
128    fn reduce_to(&self) -> Self::Result {
129        let indexer = NodeIndexer {
130            num_vars: self.num_vars(),
131            num_clauses: self.num_clauses(),
132        };
133
134        let mut arcs = Vec::<(usize, usize)>::new();
135        let mut capacities = Vec::<u64>::new();
136        let mut homologous_pairs = Vec::<(usize, usize)>::new();
137        let mut variable_paths = Vec::<VariablePaths>::with_capacity(self.num_vars());
138
139        let mut add_arc = |u: usize, v: usize, capacity: u64| -> usize {
140            arcs.push((u, v));
141            capacities.push(capacity);
142            arcs.len() - 1
143        };
144
145        for variable in 0..self.num_vars() {
146            let source_arc = add_arc(indexer.source(), indexer.split(variable), 1);
147            let true_base_arc = add_arc(
148                indexer.split(variable),
149                indexer.channel(0, variable, true),
150                1,
151            );
152            let false_base_arc = add_arc(
153                indexer.split(variable),
154                indexer.channel(0, variable, false),
155                1,
156            );
157
158            variable_paths.push(VariablePaths {
159                true_path: vec![source_arc, true_base_arc],
160                false_path: vec![source_arc, false_base_arc],
161                true_base_arc,
162            });
163        }
164
165        for (clause_idx, clause) in self.clauses().iter().enumerate() {
166            let collector = indexer.collector(clause_idx);
167            let distributor = indexer.distributor(clause_idx);
168            let bottleneck = add_arc(
169                collector,
170                distributor,
171                clause.literals.len().saturating_sub(1) as u64,
172            );
173
174            let mut has_positive = vec![false; self.num_vars()];
175            let mut has_negative = vec![false; self.num_vars()];
176            for &literal in &clause.literals {
177                let variable = literal.unsigned_abs() as usize - 1;
178                if literal > 0 {
179                    has_positive[variable] = true;
180                } else {
181                    has_negative[variable] = true;
182                }
183            }
184
185            for variable in 0..self.num_vars() {
186                let prev_true = indexer.channel(clause_idx, variable, true);
187                let prev_false = indexer.channel(clause_idx, variable, false);
188                let next_true = indexer.channel(clause_idx + 1, variable, true);
189                let next_false = indexer.channel(clause_idx + 1, variable, false);
190
191                if has_negative[variable] {
192                    let entry = add_arc(prev_true, collector, 1);
193                    let exit = add_arc(distributor, next_true, 1);
194                    homologous_pairs.push((entry, exit));
195                    variable_paths[variable]
196                        .true_path
197                        .extend([entry, bottleneck, exit]);
198                } else {
199                    let bypass = add_arc(prev_true, next_true, 1);
200                    variable_paths[variable].true_path.push(bypass);
201                }
202
203                if has_positive[variable] {
204                    let entry = add_arc(prev_false, collector, 1);
205                    let exit = add_arc(distributor, next_false, 1);
206                    homologous_pairs.push((entry, exit));
207                    variable_paths[variable]
208                        .false_path
209                        .extend([entry, bottleneck, exit]);
210                } else {
211                    let bypass = add_arc(prev_false, next_false, 1);
212                    variable_paths[variable].false_path.push(bypass);
213                }
214            }
215        }
216
217        for (variable, paths) in variable_paths.iter_mut().enumerate() {
218            let true_sink = add_arc(
219                indexer.channel(self.num_clauses(), variable, true),
220                indexer.sink(),
221                1,
222            );
223            let false_sink = add_arc(
224                indexer.channel(self.num_clauses(), variable, false),
225                indexer.sink(),
226                1,
227            );
228            paths.true_path.push(true_sink);
229            paths.false_path.push(false_sink);
230        }
231
232        let mut requirement = self.num_vars() as u64;
233        if self.clauses().iter().any(|clause| clause.is_empty()) {
234            requirement += 1;
235        }
236
237        ReductionSATToIntegralFlowHomologousArcs {
238            target: IntegralFlowHomologousArcs::new(
239                DirectedGraph::new(indexer.total_vertices(), arcs),
240                capacities,
241                indexer.source(),
242                indexer.sink(),
243                requirement,
244                homologous_pairs,
245            ),
246            variable_paths,
247        }
248    }
249}
250
251#[cfg(feature = "example-db")]
252pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
253    use crate::export::SolutionPair;
254    use crate::models::formula::CNFClause;
255
256    fn issue_example() -> Satisfiability {
257        Satisfiability::new(
258            3,
259            vec![
260                CNFClause::new(vec![1, 2]),
261                CNFClause::new(vec![-1, 3]),
262                CNFClause::new(vec![-2, -3]),
263                CNFClause::new(vec![1, 3]),
264            ],
265        )
266    }
267
268    vec![crate::example_db::specs::RuleExampleSpec {
269        id: "satisfiability_to_integralflowhomologousarcs",
270        build: || {
271            let source = issue_example();
272            let source_config = vec![1, 0, 1];
273            let target_config = ReduceTo::<IntegralFlowHomologousArcs>::reduce_to(&source)
274                .encode_assignment(&source_config);
275            crate::example_db::specs::rule_example_with_witness::<_, IntegralFlowHomologousArcs>(
276                source,
277                SolutionPair {
278                    source_config,
279                    target_config,
280                },
281            )
282        },
283    }]
284}
285
286#[cfg(test)]
287#[path = "../unit_tests/rules/satisfiability_integralflowhomologousarcs.rs"]
288mod tests;