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: i32) -> usize {
65 literal.unsigned_abs() as usize - 1
66}
67
68#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
69fn literal_satisfied(requires_true: bool, assignment: &[usize], variable: usize) -> bool {
70 assignment.get(variable).copied().unwrap_or(0) == usize::from(requires_true)
71}
72
73fn build_branch<FV, FA>(
74 add_vertex: &mut FV,
75 add_arc: &mut FA,
76 entry: usize,
77 exit: usize,
78 occurrences: &[ClauseOccurrence],
79 branch_context: &mut BranchContext<'_>,
80) -> BranchBuild
81where
82 FV: FnMut() -> usize,
83 FA: FnMut(usize, usize) -> usize,
84{
85 let mut path_arcs = Vec::with_capacity(2 * occurrences.len() + 3);
86
87 let dummy_odd = add_vertex();
88 let dummy_even = add_vertex();
89 let entry_arc = add_arc(entry, dummy_odd);
90 path_arcs.push(entry_arc);
91 path_arcs.push(add_arc(dummy_odd, dummy_even));
92
93 let mut previous_even = dummy_even;
94
95 for occurrence in occurrences {
96 let odd = add_vertex();
97 let even = add_vertex();
98 path_arcs.push(add_arc(previous_even, odd));
99 let branch_arc = add_arc(odd, even);
100 path_arcs.push(branch_arc);
101
102 let source_arc = add_arc(branch_context.source_2, odd);
103 let clause_arc = add_arc(even, branch_context.clause_vertices[occurrence.clause_idx]);
104 branch_context.clause_routes[occurrence.clause_idx].push(ClauseRoute {
105 variable: occurrence.variable,
106 requires_true: occurrence.requires_true,
107 source_arc,
108 branch_arc,
109 clause_arc,
110 });
111
112 previous_even = even;
113 }
114
115 path_arcs.push(add_arc(previous_even, exit));
116
117 BranchBuild {
118 path_arcs,
119 entry_arc,
120 }
121}
122
123impl Reduction3SATToDirectedTwoCommodityIntegralFlow {
124 #[cfg(any(test, feature = "example-db"))]
125 pub(crate) fn encode_assignment(&self, assignment: &[usize]) -> Vec<usize> {
126 assert_eq!(
127 assignment.len(),
128 self.variable_paths.len(),
129 "assignment length must match num_vars",
130 );
131
132 let num_arcs = self.target.num_arcs();
133 let mut flow = vec![0usize; 2 * num_arcs];
134
135 for &arc_idx in &self.commodity_1_chain_arcs {
136 flow[arc_idx] = 1;
137 }
138
139 for (value, paths) in assignment.iter().zip(&self.variable_paths) {
140 let chosen_path = if *value == 1 {
141 &paths.lower_path
142 } else {
143 &paths.upper_path
144 };
145 for &arc_idx in chosen_path {
146 flow[arc_idx] = 1;
147 }
148 }
149
150 for (clause_idx, routes) in self.clause_routes.iter().enumerate() {
151 if let Some(route) = routes
152 .iter()
153 .find(|route| literal_satisfied(route.requires_true, assignment, route.variable))
154 {
155 flow[num_arcs + route.source_arc] = 1;
156 flow[num_arcs + route.branch_arc] = 1;
157 flow[num_arcs + route.clause_arc] = 1;
158 flow[num_arcs + self.clause_sink_arcs[clause_idx]] = 1;
159 }
160 }
161
162 flow
163 }
164}
165
166impl ReductionResult for Reduction3SATToDirectedTwoCommodityIntegralFlow {
167 type Source = KSatisfiability<K3>;
168 type Target = DirectedTwoCommodityIntegralFlow;
169
170 fn target_problem(&self) -> &Self::Target {
171 &self.target
172 }
173
174 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
175 self.variable_paths
176 .iter()
177 .map(|paths| {
178 usize::from(
179 target_solution
180 .get(paths.lower_entry_arc)
181 .copied()
182 .unwrap_or(0)
183 > 0,
184 )
185 })
186 .collect()
187 }
188}
189
190#[reduction(overhead = {
191 num_vertices = "6 * num_vars + 2 * num_literals + num_clauses + 4",
192 num_arcs = "7 * num_vars + 4 * num_literals + num_clauses + 1",
193})]
194impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
195 type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
196
197 fn reduce_to(&self) -> Self::Result {
198 let source_1 = 0usize;
199 let sink_1 = 1usize;
200 let source_2 = 2usize;
201 let sink_2 = 3usize;
202
203 let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
204 let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
205 for (clause_idx, clause) in self.clauses().iter().enumerate() {
206 for &literal in &clause.literals {
207 let variable = literal_var_index(literal);
208 let occurrence = ClauseOccurrence {
209 clause_idx,
210 variable,
211 requires_true: literal > 0,
212 };
213 if literal > 0 {
214 positive_occurrences[variable].push(occurrence);
215 } else {
216 negative_occurrences[variable].push(occurrence);
217 }
218 }
219 }
220
221 let mut next_vertex = 4 + self.num_clauses();
222 let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
223 let mut add_vertex = || {
224 let id = next_vertex;
225 next_vertex += 1;
226 id
227 };
228
229 let mut arcs = Vec::<(usize, usize)>::new();
230 let mut add_arc = |u: usize, v: usize| {
231 arcs.push((u, v));
232 arcs.len() - 1
233 };
234
235 let mut entries = Vec::with_capacity(self.num_vars());
236 let mut exits = Vec::with_capacity(self.num_vars());
237 let mut variable_paths = Vec::with_capacity(self.num_vars());
238 let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
239 let mut branch_context = BranchContext {
240 source_2,
241 clause_vertices: &clause_vertices,
242 clause_routes: &mut clause_routes,
243 };
244
245 for variable in 0..self.num_vars() {
246 let entry = add_vertex();
247 let exit = add_vertex();
248 entries.push(entry);
249 exits.push(exit);
250
251 let upper = build_branch(
252 &mut add_vertex,
253 &mut add_arc,
254 entry,
255 exit,
256 &positive_occurrences[variable],
257 &mut branch_context,
258 );
259 let lower = build_branch(
260 &mut add_vertex,
261 &mut add_arc,
262 entry,
263 exit,
264 &negative_occurrences[variable],
265 &mut branch_context,
266 );
267
268 variable_paths.push(VariablePaths {
269 upper_path: upper.path_arcs,
270 lower_path: lower.path_arcs,
271 lower_entry_arc: lower.entry_arc,
272 });
273 }
274
275 let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
276 if self.num_vars() == 0 {
277 commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
278 } else {
279 commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
280 for variable in 0..self.num_vars() - 1 {
281 commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
282 }
283 commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
284 }
285
286 let clause_sink_arcs: Vec<usize> = clause_vertices
287 .iter()
288 .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
289 .collect();
290
291 let capacities = vec![1u64; arcs.len()];
292 let target = DirectedTwoCommodityIntegralFlow::new(
293 DirectedGraph::new(next_vertex, arcs),
294 capacities,
295 source_1,
296 sink_1,
297 source_2,
298 sink_2,
299 1,
300 self.num_clauses() as u64,
301 );
302
303 Reduction3SATToDirectedTwoCommodityIntegralFlow {
304 target,
305 commodity_1_chain_arcs,
306 variable_paths,
307 clause_routes,
308 clause_sink_arcs,
309 }
310 }
311}
312
313#[cfg(feature = "example-db")]
314pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
315 use crate::export::SolutionPair;
316
317 vec![crate::example_db::specs::RuleExampleSpec {
318 id: "ksatisfiability_to_directedtwocommodityintegralflow",
319 build: || {
320 let source = KSatisfiability::<K3>::new(
321 3,
322 vec![
323 crate::models::formula::CNFClause::new(vec![1, -2, 3]),
324 crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
325 ],
326 );
327 let reduction =
328 crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source);
329 let source_config = vec![1, 1, 0];
330 let target_config = reduction.encode_assignment(&source_config);
331
332 crate::example_db::specs::assemble_rule_example(
333 &source,
334 reduction.target_problem(),
335 vec![SolutionPair {
336 source_config,
337 target_config,
338 }],
339 )
340 },
341 }]
342}
343
344#[cfg(test)]
345#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
346mod tests;