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