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: &[bool]) -> 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 {
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(
106        &self,
107        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
108    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
109        crate::rules::traits::validate_target_witness(
110            self.target_problem(),
111            target_solution,
112            |value| value.0,
113            "target flow is not feasible",
114        )?;
115
116        Ok({
117            self.variable_paths
118                .iter()
119                .map(|paths| target_solution[paths.true_base_arc] > 0)
120                .collect()
121        })
122    }
123}
124
125#[crate::aggregate_reduction(identity)]
126impl crate::rules::AggregateReductionResult for ReductionSATToIntegralFlowHomologousArcs {}
127
128#[reduction(transform = upper_bound {
129    num_vertices = "2 * num_vars * num_clauses + 3 * num_vars + 2 * num_clauses + 2",
130    num_arcs = "2 * num_vars * num_clauses + 5 * num_vars + num_clauses + num_literals",
131    max_capacity = "num_literals + 1",
132    // One pair per distinct signed clause literal. Capacity is 1 or at most L-1.
133    num_homologous_pairs = "num_literals",
134    max_capacity_bits = "num_literals / 2 + 2",
135})]
136impl ReduceTo<IntegralFlowHomologousArcs> for Satisfiability {
137    type Result = ReductionSATToIntegralFlowHomologousArcs;
138
139    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
140        let indexer = NodeIndexer {
141            num_vars: self.num_vars(),
142            num_clauses: self.num_clauses(),
143        };
144
145        let mut arcs = Vec::<(usize, usize)>::new();
146        let mut capacities = Vec::<i64>::new();
147        let mut homologous_pairs = Vec::<(usize, usize)>::new();
148        let mut variable_paths = Vec::<VariablePaths>::with_capacity(self.num_vars());
149
150        let mut add_arc = |u: usize, v: usize, capacity: i64| -> usize {
151            arcs.push((u, v));
152            capacities.push(capacity);
153            arcs.len() - 1
154        };
155
156        for variable in 0..self.num_vars() {
157            let source_arc = add_arc(indexer.source(), indexer.split(variable), 1);
158            let true_base_arc = add_arc(
159                indexer.split(variable),
160                indexer.channel(0, variable, true),
161                1,
162            );
163            let false_base_arc = add_arc(
164                indexer.split(variable),
165                indexer.channel(0, variable, false),
166                1,
167            );
168
169            variable_paths.push(VariablePaths {
170                true_path: vec![source_arc, true_base_arc],
171                false_path: vec![source_arc, false_base_arc],
172                true_base_arc,
173            });
174        }
175
176        for (clause_idx, clause) in self.clauses().iter().enumerate() {
177            let collector = indexer.collector(clause_idx);
178            let distributor = indexer.distributor(clause_idx);
179            // Repeated literals share one flow channel and count only once.
180            let distinct_literals: std::collections::BTreeSet<_> = clause.literals.iter().collect();
181            let bottleneck_capacity = i64::try_from(distinct_literals.len().saturating_sub(1))
182                .map_err(|_| {
183                    crate::rules::ReductionError::integer_overflow::<
184                        Satisfiability,
185                        IntegralFlowHomologousArcs,
186                    >("converting a clause bottleneck capacity to i64")
187                })?;
188            let bottleneck = add_arc(collector, distributor, bottleneck_capacity);
189
190            let mut has_positive = vec![false; self.num_vars()];
191            let mut has_negative = vec![false; self.num_vars()];
192            for &literal in &clause.literals {
193                let variable = literal.unsigned_abs() as usize - 1;
194                if literal > 0 {
195                    has_positive[variable] = true;
196                } else {
197                    has_negative[variable] = true;
198                }
199            }
200
201            for variable in 0..self.num_vars() {
202                let prev_true = indexer.channel(clause_idx, variable, true);
203                let prev_false = indexer.channel(clause_idx, variable, false);
204                let next_true = indexer.channel(clause_idx + 1, variable, true);
205                let next_false = indexer.channel(clause_idx + 1, variable, false);
206
207                if has_negative[variable] {
208                    let entry = add_arc(prev_true, collector, 1);
209                    let exit = add_arc(distributor, next_true, 1);
210                    homologous_pairs.push((entry, exit));
211                    variable_paths[variable]
212                        .true_path
213                        .extend([entry, bottleneck, exit]);
214                } else {
215                    let bypass = add_arc(prev_true, next_true, 1);
216                    variable_paths[variable].true_path.push(bypass);
217                }
218
219                if has_positive[variable] {
220                    let entry = add_arc(prev_false, collector, 1);
221                    let exit = add_arc(distributor, next_false, 1);
222                    homologous_pairs.push((entry, exit));
223                    variable_paths[variable]
224                        .false_path
225                        .extend([entry, bottleneck, exit]);
226                } else {
227                    let bypass = add_arc(prev_false, next_false, 1);
228                    variable_paths[variable].false_path.push(bypass);
229                }
230            }
231        }
232
233        for (variable, paths) in variable_paths.iter_mut().enumerate() {
234            let true_sink = add_arc(
235                indexer.channel(self.num_clauses(), variable, true),
236                indexer.sink(),
237                1,
238            );
239            let false_sink = add_arc(
240                indexer.channel(self.num_clauses(), variable, false),
241                indexer.sink(),
242                1,
243            );
244            paths.true_path.push(true_sink);
245            paths.false_path.push(false_sink);
246        }
247
248        let mut requirement = i64::try_from(self.num_vars()).map_err(|_| {
249            crate::rules::ReductionError::integer_overflow::<
250                Satisfiability,
251                IntegralFlowHomologousArcs,
252            >("converting the SAT variable count to an i64 flow requirement")
253        })?;
254        if self.clauses().iter().any(|clause| clause.is_empty()) {
255            requirement = requirement.checked_add(1).ok_or_else(|| {
256                crate::rules::ReductionError::integer_overflow::<
257                    Satisfiability,
258                    IntegralFlowHomologousArcs,
259                >("including the empty-clause flow requirement")
260            })?;
261        }
262
263        Ok(ReductionSATToIntegralFlowHomologousArcs {
264            target: IntegralFlowHomologousArcs::new(
265                DirectedGraph::new(indexer.total_vertices(), arcs),
266                capacities,
267                indexer.source(),
268                indexer.sink(),
269                requirement,
270                homologous_pairs,
271            ),
272            variable_paths,
273        })
274    }
275}
276
277#[cfg(feature = "example-db")]
278pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
279    use crate::export::SolutionPair;
280    use crate::models::formula::CNFClause;
281
282    fn issue_example() -> Satisfiability {
283        Satisfiability::new(
284            3,
285            vec![
286                CNFClause::new(vec![1, 2]),
287                CNFClause::new(vec![-1, 3]),
288                CNFClause::new(vec![-2, -3]),
289                CNFClause::new(vec![1, 3]),
290            ],
291        )
292    }
293
294    vec![crate::example_db::specs::RuleExampleSpec {
295        id: "satisfiability_to_integralflowhomologousarcs",
296        build: || {
297            let source = issue_example();
298            let source_config = vec![true, false, true];
299            let target_config = ReduceTo::<IntegralFlowHomologousArcs>::reduce_to(&source)
300                .expect("reduction should succeed")
301                .encode_assignment(&source_config);
302            crate::example_db::specs::rule_example_with_witness::<_, IntegralFlowHomologousArcs>(
303                source,
304                SolutionPair {
305                    source_config: serde_json::to_value(source_config)
306                        .expect("solution serialization must succeed"),
307                    target_config: serde_json::to_value(target_config)
308                        .expect("solution serialization must succeed"),
309                },
310            )
311        },
312    }]
313}
314
315#[cfg(test)]
316#[path = "../unit_tests/rules/satisfiability_integralflowhomologousarcs.rs"]
317mod tests;