1use crate::models::formula::KSatisfiability;
11use crate::models::graph::DirectedTwoCommodityIntegralFlow;
12use crate::reduction;
13use crate::rules::traits::{ReduceTo, ReductionResult};
14use crate::topology::DirectedGraph;
15use crate::variant::K3;
16
17#[derive(Debug, Clone, Copy)]
18struct ClauseOccurrence {
19 clause_idx: usize,
20 variable: usize,
21 requires_true: bool,
22}
23
24#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
25#[derive(Debug, Clone)]
26struct VariablePaths {
27 upper_path: Vec<usize>,
28 lower_path: Vec<usize>,
29 lower_entry_arc: usize,
30}
31
32#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
33#[derive(Debug, Clone)]
34struct ClauseRoute {
35 variable: usize,
36 requires_true: bool,
37 source_arc: usize,
38 branch_arc: usize,
39 clause_arc: usize,
40}
41
42#[derive(Debug, Clone)]
43struct BranchBuild {
44 path_arcs: Vec<usize>,
45 entry_arc: usize,
46}
47
48struct BranchContext<'a> {
49 source_2: usize,
50 clause_vertices: &'a [usize],
51 clause_routes: &'a mut [Vec<ClauseRoute>],
52}
53
54#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
55#[derive(Debug, Clone)]
56pub struct Reduction3SATToDirectedTwoCommodityIntegralFlow {
57 target: DirectedTwoCommodityIntegralFlow,
58 commodity_1_chain_arcs: Vec<usize>,
59 variable_paths: Vec<VariablePaths>,
60 clause_routes: Vec<Vec<ClauseRoute>>,
61 clause_sink_arcs: Vec<usize>,
62}
63
64fn literal_var_index(literal: i64) -> usize {
65 literal.unsigned_abs() as usize - 1
66}
67
68fn build_branch<FV, FA>(
69 add_vertex: &mut FV,
70 add_arc: &mut FA,
71 entry: usize,
72 exit: usize,
73 occurrences: &[ClauseOccurrence],
74 branch_context: &mut BranchContext<'_>,
75) -> BranchBuild
76where
77 FV: FnMut() -> usize,
78 FA: FnMut(usize, usize) -> usize,
79{
80 let mut path_arcs = Vec::with_capacity(2 * occurrences.len() + 3);
81
82 let dummy_odd = add_vertex();
83 let dummy_even = add_vertex();
84 let entry_arc = add_arc(entry, dummy_odd);
85 path_arcs.push(entry_arc);
86 path_arcs.push(add_arc(dummy_odd, dummy_even));
87
88 let mut previous_even = dummy_even;
89
90 for occurrence in occurrences {
91 let odd = add_vertex();
92 let even = add_vertex();
93 path_arcs.push(add_arc(previous_even, odd));
94 let branch_arc = add_arc(odd, even);
95 path_arcs.push(branch_arc);
96
97 let source_arc = add_arc(branch_context.source_2, odd);
98 let clause_arc = add_arc(even, branch_context.clause_vertices[occurrence.clause_idx]);
99 branch_context.clause_routes[occurrence.clause_idx].push(ClauseRoute {
100 variable: occurrence.variable,
101 requires_true: occurrence.requires_true,
102 source_arc,
103 branch_arc,
104 clause_arc,
105 });
106
107 previous_even = even;
108 }
109
110 path_arcs.push(add_arc(previous_even, exit));
111
112 BranchBuild {
113 path_arcs,
114 entry_arc,
115 }
116}
117
118impl Reduction3SATToDirectedTwoCommodityIntegralFlow {
119 #[cfg(any(test, feature = "example-db"))]
120 pub(crate) fn encode_assignment(&self, assignment: &[bool]) -> Vec<usize> {
121 assert_eq!(
122 assignment.len(),
123 self.variable_paths.len(),
124 "assignment length must match num_vars",
125 );
126
127 let num_arcs = self.target.num_arcs();
128 let mut flow = vec![0usize; 2 * num_arcs];
129
130 for &arc_idx in &self.commodity_1_chain_arcs {
131 flow[arc_idx] = 1;
132 }
133
134 for (value, paths) in assignment.iter().zip(&self.variable_paths) {
135 let chosen_path = if *value {
136 &paths.lower_path
137 } else {
138 &paths.upper_path
139 };
140 for &arc_idx in chosen_path {
141 flow[arc_idx] = 1;
142 }
143 }
144
145 for (clause_idx, routes) in self.clause_routes.iter().enumerate() {
146 if let Some(route) = routes
147 .iter()
148 .find(|route| assignment[route.variable] == route.requires_true)
149 {
150 flow[num_arcs + route.source_arc] = 1;
151 flow[num_arcs + route.branch_arc] = 1;
152 flow[num_arcs + route.clause_arc] = 1;
153 flow[num_arcs + self.clause_sink_arcs[clause_idx]] = 1;
154 }
155 }
156
157 flow
158 }
159}
160
161impl ReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {
162 type Source = KSatisfiability<K3>;
163 type Target = DirectedTwoCommodityIntegralFlow;
164
165 fn target_problem(&self) -> &Self::Target {
166 &self.target
167 }
168
169 fn extract_solution(
170 &self,
171 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
172 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
173 crate::rules::traits::validate_target_witness(
174 self.target_problem(),
175 target_solution,
176 |value| value.0,
177 "target witness does not satisfy the target problem",
178 )?;
179
180 Ok({
181 self.variable_paths
182 .iter()
183 .map(|paths| target_solution[paths.lower_entry_arc] > 0)
184 .collect()
185 })
186 }
187}
188
189#[crate::aggregate_reduction(identity)]
190impl crate::rules::AggregateReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {}
191
192#[reduction(transform = exact {
193 num_vertices = "6 * num_vars + 2 * num_literals + num_clauses + 4",
194 num_arcs = "7 * num_vars + 4 * num_literals + num_clauses + 1",
195 max_capacity = "1",
196})]
197impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
198 type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
199
200 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
201 let source_1 = 0usize;
202 let sink_1 = 1usize;
203 let source_2 = 2usize;
204 let sink_2 = 3usize;
205
206 let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
207 let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
208 for (clause_idx, clause) in self.clauses().iter().enumerate() {
209 for &literal in &clause.literals {
210 let variable = literal_var_index(literal);
211 let occurrence = ClauseOccurrence {
212 clause_idx,
213 variable,
214 requires_true: literal > 0,
215 };
216 if literal > 0 {
217 positive_occurrences[variable].push(occurrence);
218 } else {
219 negative_occurrences[variable].push(occurrence);
220 }
221 }
222 }
223
224 let mut next_vertex = 4 + self.num_clauses();
225 let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
226 let mut add_vertex = || {
227 let id = next_vertex;
228 next_vertex += 1;
229 id
230 };
231
232 let mut arcs = Vec::<(usize, usize)>::new();
233 let mut add_arc = |u: usize, v: usize| {
234 arcs.push((u, v));
235 arcs.len() - 1
236 };
237
238 let mut entries = Vec::with_capacity(self.num_vars());
239 let mut exits = Vec::with_capacity(self.num_vars());
240 let mut variable_paths = Vec::with_capacity(self.num_vars());
241 let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
242 let mut branch_context = BranchContext {
243 source_2,
244 clause_vertices: &clause_vertices,
245 clause_routes: &mut clause_routes,
246 };
247
248 for variable in 0..self.num_vars() {
249 let entry = add_vertex();
250 let exit = add_vertex();
251 entries.push(entry);
252 exits.push(exit);
253
254 let upper = build_branch(
255 &mut add_vertex,
256 &mut add_arc,
257 entry,
258 exit,
259 &positive_occurrences[variable],
260 &mut branch_context,
261 );
262 let lower = build_branch(
263 &mut add_vertex,
264 &mut add_arc,
265 entry,
266 exit,
267 &negative_occurrences[variable],
268 &mut branch_context,
269 );
270
271 variable_paths.push(VariablePaths {
272 upper_path: upper.path_arcs,
273 lower_path: lower.path_arcs,
274 lower_entry_arc: lower.entry_arc,
275 });
276 }
277
278 let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
279 if self.num_vars() == 0 {
280 commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
281 } else {
282 commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
283 for variable in 0..self.num_vars() - 1 {
284 commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
285 }
286 commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
287 }
288
289 let clause_sink_arcs: Vec<usize> = clause_vertices
290 .iter()
291 .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
292 .collect();
293
294 let capacities = vec![1i64; arcs.len()];
295 let clause_requirement = i64::try_from(self.num_clauses()).map_err(|_| {
296 crate::rules::ReductionError::integer_overflow::<
297 KSatisfiability<K3>,
298 DirectedTwoCommodityIntegralFlow,
299 >("converting the clause count to an i64 flow requirement")
300 })?;
301 let target = DirectedTwoCommodityIntegralFlow::new(
302 DirectedGraph::new(next_vertex, arcs),
303 capacities,
304 source_1,
305 sink_1,
306 source_2,
307 sink_2,
308 1,
309 clause_requirement,
310 );
311
312 Ok(Reduction3SATToDirectedTwoCommodityIntegralFlow {
313 target,
314 commodity_1_chain_arcs,
315 variable_paths,
316 clause_routes,
317 clause_sink_arcs,
318 })
319 }
320}
321
322#[cfg(feature = "example-db")]
323pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
324 use crate::export::SolutionPair;
325
326 vec![crate::example_db::specs::RuleExampleSpec {
327 id: "ksatisfiability_to_directedtwocommodityintegralflow",
328 build: || {
329 let source = KSatisfiability::<K3>::new(
330 3,
331 vec![
332 crate::models::formula::CNFClause::new(vec![1, -2, 3]),
333 crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
334 ],
335 );
336 let reduction =
337 crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source)
338 .expect("reduction should succeed");
339 let source_config = vec![true, true, false];
340 let target_config = reduction.encode_assignment(&source_config);
341
342 crate::example_db::specs::assemble_rule_example(
343 &source,
344 reduction.target_problem(),
345 vec![SolutionPair {
346 source_config: serde_json::to_value(source_config)
347 .expect("solution serialization must succeed"),
348 target_config: serde_json::to_value(target_config)
349 .expect("solution serialization must succeed"),
350 }],
351 )
352 },
353 }]
354}
355
356#[cfg(test)]
357#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
358mod tests;