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
68#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
69fn literal_satisfied(requires_true: bool, assignment: &[bool], variable: usize) -> bool {
70 assignment.get(variable).copied().unwrap_or(false) == 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: &[bool]) -> 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 {
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(
175 &self,
176 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
177 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
178 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
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#[reduction(
190 transform = exact {
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 },
194 unavailable = {
195 max_capacity = "the exact target parameter is not represented by this reduction's symbolic transform",
196 }
197)]
198impl ReduceTo<DirectedTwoCommodityIntegralFlow> for KSatisfiability<K3> {
199 type Result = Reduction3SATToDirectedTwoCommodityIntegralFlow;
200
201 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
202 let source_1 = 0usize;
203 let sink_1 = 1usize;
204 let source_2 = 2usize;
205 let sink_2 = 3usize;
206
207 let mut positive_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
208 let mut negative_occurrences = vec![Vec::<ClauseOccurrence>::new(); self.num_vars()];
209 for (clause_idx, clause) in self.clauses().iter().enumerate() {
210 for &literal in &clause.literals {
211 let variable = literal_var_index(literal);
212 let occurrence = ClauseOccurrence {
213 clause_idx,
214 variable,
215 requires_true: literal > 0,
216 };
217 if literal > 0 {
218 positive_occurrences[variable].push(occurrence);
219 } else {
220 negative_occurrences[variable].push(occurrence);
221 }
222 }
223 }
224
225 let mut next_vertex = 4 + self.num_clauses();
226 let clause_vertices: Vec<usize> = (0..self.num_clauses()).map(|idx| 4 + idx).collect();
227 let mut add_vertex = || {
228 let id = next_vertex;
229 next_vertex += 1;
230 id
231 };
232
233 let mut arcs = Vec::<(usize, usize)>::new();
234 let mut add_arc = |u: usize, v: usize| {
235 arcs.push((u, v));
236 arcs.len() - 1
237 };
238
239 let mut entries = Vec::with_capacity(self.num_vars());
240 let mut exits = Vec::with_capacity(self.num_vars());
241 let mut variable_paths = Vec::with_capacity(self.num_vars());
242 let mut clause_routes = vec![Vec::<ClauseRoute>::new(); self.num_clauses()];
243 let mut branch_context = BranchContext {
244 source_2,
245 clause_vertices: &clause_vertices,
246 clause_routes: &mut clause_routes,
247 };
248
249 for variable in 0..self.num_vars() {
250 let entry = add_vertex();
251 let exit = add_vertex();
252 entries.push(entry);
253 exits.push(exit);
254
255 let upper = build_branch(
256 &mut add_vertex,
257 &mut add_arc,
258 entry,
259 exit,
260 &positive_occurrences[variable],
261 &mut branch_context,
262 );
263 let lower = build_branch(
264 &mut add_vertex,
265 &mut add_arc,
266 entry,
267 exit,
268 &negative_occurrences[variable],
269 &mut branch_context,
270 );
271
272 variable_paths.push(VariablePaths {
273 upper_path: upper.path_arcs,
274 lower_path: lower.path_arcs,
275 lower_entry_arc: lower.entry_arc,
276 });
277 }
278
279 let mut commodity_1_chain_arcs = Vec::with_capacity(self.num_vars() + 1);
280 if self.num_vars() == 0 {
281 commodity_1_chain_arcs.push(add_arc(source_1, sink_1));
282 } else {
283 commodity_1_chain_arcs.push(add_arc(source_1, entries[0]));
284 for variable in 0..self.num_vars() - 1 {
285 commodity_1_chain_arcs.push(add_arc(exits[variable], entries[variable + 1]));
286 }
287 commodity_1_chain_arcs.push(add_arc(exits[self.num_vars() - 1], sink_1));
288 }
289
290 let clause_sink_arcs: Vec<usize> = clause_vertices
291 .iter()
292 .map(|&clause_vertex| add_arc(clause_vertex, sink_2))
293 .collect();
294
295 let capacities = vec![1i64; arcs.len()];
296 let clause_requirement = i64::try_from(self.num_clauses()).map_err(|_| {
297 crate::rules::ReductionError::integer_overflow::<
298 KSatisfiability<K3>,
299 DirectedTwoCommodityIntegralFlow,
300 >("converting the clause count to an i64 flow requirement")
301 })?;
302 let target = DirectedTwoCommodityIntegralFlow::new(
303 DirectedGraph::new(next_vertex, arcs),
304 capacities,
305 source_1,
306 sink_1,
307 source_2,
308 sink_2,
309 1,
310 clause_requirement,
311 );
312
313 Ok(Reduction3SATToDirectedTwoCommodityIntegralFlow {
314 target,
315 commodity_1_chain_arcs,
316 variable_paths,
317 clause_routes,
318 clause_sink_arcs,
319 })
320 }
321}
322
323#[cfg(feature = "example-db")]
324pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
325 use crate::export::SolutionPair;
326
327 vec![crate::example_db::specs::RuleExampleSpec {
328 id: "ksatisfiability_to_directedtwocommodityintegralflow",
329 build: || {
330 let source = KSatisfiability::<K3>::new(
331 3,
332 vec![
333 crate::models::formula::CNFClause::new(vec![1, -2, 3]),
334 crate::models::formula::CNFClause::new(vec![-1, 2, -3]),
335 ],
336 );
337 let reduction =
338 crate::rules::ReduceTo::<DirectedTwoCommodityIntegralFlow>::reduce_to(&source)
339 .expect("reduction should succeed");
340 let source_config = vec![true, true, false];
341 let target_config = reduction.encode_assignment(&source_config);
342
343 crate::example_db::specs::assemble_rule_example(
344 &source,
345 reduction.target_problem(),
346 vec![SolutionPair {
347 source_config: serde_json::to_value(source_config)
348 .expect("solution serialization must succeed"),
349 target_config: serde_json::to_value(target_config)
350 .expect("solution serialization must succeed"),
351 }],
352 )
353 },
354 }]
355}
356
357#[cfg(test)]
358#[path = "../unit_tests/rules/ksatisfiability_directedtwocommodityintegralflow.rs"]
359mod tests;