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_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;