1use 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#[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 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 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;