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: &[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;