1use crate::models::misc::ThreePartition;
9use crate::models::set::ThreeDimensionalMatching;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13#[derive(Debug, Clone, Copy)]
14enum Step2Item {
15 A {
16 source_triple: usize,
17 w: usize,
18 x: usize,
19 y: usize,
20 },
21 B {
22 w: usize,
23 first_occurrence: bool,
24 },
25 C {
26 x: usize,
27 first_occurrence: bool,
28 },
29 D {
30 y: usize,
31 first_occurrence: bool,
32 },
33}
34
35#[derive(Debug, Clone)]
37pub struct ReductionThreeDimensionalMatchingToThreePartition {
38 target: ThreePartition,
39 step2_items: Vec<Step2Item>,
40 pair_keys: Vec<(usize, usize)>,
41 num_source_triples: usize,
42}
43
44impl ReductionThreeDimensionalMatchingToThreePartition {
45 fn num_regulars(&self) -> usize {
46 self.step2_items.len()
47 }
48
49 fn pairing_start(&self) -> usize {
50 self.num_regulars()
51 }
52
53 fn filler_start(&self) -> usize {
54 self.pairing_start() + 2 * self.pair_keys.len()
55 }
56
57 fn decode_real_group(&self, step2_group: [usize; 4]) -> Option<usize> {
58 let mut a_item = None;
59 let mut b_item = None;
60 let mut c_item = None;
61 let mut d_item = None;
62
63 for step2_index in step2_group {
64 match self.step2_items[step2_index] {
65 Step2Item::A {
66 source_triple,
67 w,
68 x,
69 y,
70 } => {
71 a_item = Some((source_triple, w, x, y));
72 }
73 Step2Item::B {
74 w,
75 first_occurrence,
76 } => {
77 b_item = Some((w, first_occurrence));
78 }
79 Step2Item::C {
80 x,
81 first_occurrence,
82 } => {
83 c_item = Some((x, first_occurrence));
84 }
85 Step2Item::D {
86 y,
87 first_occurrence,
88 } => {
89 d_item = Some((y, first_occurrence));
90 }
91 }
92 }
93
94 let (source_triple, aw, ax, ay) = a_item?;
95 let (bw, b_first) = b_item?;
96 let (cx, c_first) = c_item?;
97 let (dy, d_first) = d_item?;
98
99 if aw != bw || ax != cx || ay != dy {
100 return None;
101 }
102
103 if b_first && c_first && d_first {
104 Some(source_triple)
105 } else {
106 None
107 }
108 }
109
110 #[cfg(test)]
111 fn build_target_witness(&self, source_solution: &[usize]) -> Vec<usize> {
112 use std::collections::HashMap;
113
114 let mut a_indices = vec![0usize; self.num_source_triples];
115 let mut first_b_by_w = HashMap::new();
116 let mut first_c_by_x = HashMap::new();
117 let mut first_d_by_y = HashMap::new();
118 let mut dummy_bs_by_w: HashMap<usize, Vec<usize>> = HashMap::new();
119 let mut dummy_cs_by_x: HashMap<usize, Vec<usize>> = HashMap::new();
120 let mut dummy_ds_by_y: HashMap<usize, Vec<usize>> = HashMap::new();
121
122 for (step2_index, item) in self.step2_items.iter().copied().enumerate() {
123 match item {
124 Step2Item::A { source_triple, .. } => {
125 a_indices[source_triple] = step2_index;
126 }
127 Step2Item::B {
128 w,
129 first_occurrence,
130 } => {
131 if first_occurrence {
132 first_b_by_w.insert(w, step2_index);
133 } else {
134 dummy_bs_by_w.entry(w).or_default().push(step2_index);
135 }
136 }
137 Step2Item::C {
138 x,
139 first_occurrence,
140 } => {
141 if first_occurrence {
142 first_c_by_x.insert(x, step2_index);
143 } else {
144 dummy_cs_by_x.entry(x).or_default().push(step2_index);
145 }
146 }
147 Step2Item::D {
148 y,
149 first_occurrence,
150 } => {
151 if first_occurrence {
152 first_d_by_y.insert(y, step2_index);
153 } else {
154 dummy_ds_by_y.entry(y).or_default().push(step2_index);
155 }
156 }
157 }
158 }
159
160 let mut step2_groups = Vec::with_capacity(self.num_source_triples);
161 for source_triple in 0..self.num_source_triples {
162 let Step2Item::A { w, x, y, .. } = self.step2_items[a_indices[source_triple]] else {
163 unreachable!("A indices are populated from A items");
164 };
165
166 let group = if source_solution[source_triple] == 1 {
167 [
168 a_indices[source_triple],
169 *first_b_by_w
170 .get(&w)
171 .expect("selected triple must have a first-occurrence B item"),
172 *first_c_by_x
173 .get(&x)
174 .expect("selected triple must have a first-occurrence C item"),
175 *first_d_by_y
176 .get(&y)
177 .expect("selected triple must have a first-occurrence D item"),
178 ]
179 } else {
180 [
181 a_indices[source_triple],
182 dummy_bs_by_w
183 .get_mut(&w)
184 .and_then(|items| items.pop())
185 .expect("unselected triple must have a dummy B item"),
186 dummy_cs_by_x
187 .get_mut(&x)
188 .and_then(|items| items.pop())
189 .expect("unselected triple must have a dummy C item"),
190 dummy_ds_by_y
191 .get_mut(&y)
192 .and_then(|items| items.pop())
193 .expect("unselected triple must have a dummy D item"),
194 ]
195 };
196
197 step2_groups.push(group);
198 }
199
200 let pair_to_index: HashMap<(usize, usize), usize> = self
201 .pair_keys
202 .iter()
203 .copied()
204 .enumerate()
205 .map(|(pair_index, pair)| (pair, pair_index))
206 .collect();
207 let mut pair_used = vec![false; self.pair_keys.len()];
208 let mut target_solution = vec![0usize; self.target.num_elements()];
209 let mut next_group = 0usize;
210
211 for mut step2_group in step2_groups {
212 step2_group.sort_unstable();
213 let pair_key = (step2_group[0], step2_group[1]);
214 let pair_index = *pair_to_index
215 .get(&pair_key)
216 .expect("chosen regular pair must exist in the pairing gadget");
217 pair_used[pair_index] = true;
218
219 let u_index = self.pairing_start() + 2 * pair_index;
220 let uprime_index = u_index + 1;
221
222 target_solution[step2_group[0]] = next_group;
223 target_solution[step2_group[1]] = next_group;
224 target_solution[u_index] = next_group;
225 next_group += 1;
226
227 target_solution[step2_group[2]] = next_group;
228 target_solution[step2_group[3]] = next_group;
229 target_solution[uprime_index] = next_group;
230 next_group += 1;
231 }
232
233 let mut filler_index = self.filler_start();
234 for (pair_index, used) in pair_used.into_iter().enumerate() {
235 if used {
236 continue;
237 }
238
239 let u_index = self.pairing_start() + 2 * pair_index;
240 let uprime_index = u_index + 1;
241 target_solution[u_index] = next_group;
242 target_solution[uprime_index] = next_group;
243 target_solution[filler_index] = next_group;
244 filler_index += 1;
245 next_group += 1;
246 }
247
248 assert_eq!(filler_index, self.target.num_elements());
249 assert_eq!(next_group, self.target.num_groups());
250
251 target_solution
252 }
253}
254
255impl ReductionResult for ReductionThreeDimensionalMatchingToThreePartition {
256 type Source = ThreeDimensionalMatching;
257 type Target = ThreePartition;
258
259 fn target_problem(&self) -> &Self::Target {
260 &self.target
261 }
262
263 fn extract_solution(
266 &self,
267 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
268 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
269 crate::rules::traits::validate_target_witness(
270 self.target_problem(),
271 target_solution,
272 |value| value.0,
273 "target assignment is not a feasible 3-partition",
274 )?;
275
276 if self.num_source_triples == 0 {
277 return Ok(Vec::new());
278 }
279 let mut groups = vec![Vec::with_capacity(3); self.target.num_groups()];
280 let mut positions = Vec::with_capacity(target_solution.len());
281 for (element, &group) in target_solution.iter().enumerate() {
282 positions.push((group, groups[group].len()));
283 groups[group].push(element);
284 }
285
286 let pairing_start = self.pairing_start();
295 for filler in self.filler_start()..target_solution.len() {
296 let (group, _) = positions[filler];
297 let first = groups[group][0];
298 let second = groups[group][1];
299 let mate = pairing_start + ((first - pairing_start) ^ 1);
300 let (mate_group, mate_slot) = positions[mate];
301 groups[group][1] = mate;
302 groups[mate_group][mate_slot] = second;
303 positions[mate] = (group, 1);
304 positions[second] = (mate_group, mate_slot);
305 }
306
307 let mut source_solution = vec![false; self.num_source_triples];
308 for first in (pairing_start..self.filler_start()).step_by(2) {
309 let (left, _) = positions[first];
310 if groups[left][0] >= pairing_start {
311 continue; }
313 let (right, _) = positions[first + 1];
314 let regulars = [
317 groups[left][0],
318 groups[left][1],
319 groups[right][0],
320 groups[right][1],
321 ];
322 if let Some(source_triple) = self.decode_real_group(regulars) {
323 source_solution[source_triple] = true;
324 }
325 }
326 Ok(source_solution)
327 }
328}
329
330fn enumerate_pair_keys(num_regulars: usize) -> Option<Vec<(usize, usize)>> {
331 let capacity = num_regulars
332 .checked_mul(num_regulars.saturating_sub(1))
333 .and_then(|value| value.checked_div(2))?;
334 let mut pairs = Vec::with_capacity(capacity);
335 for left in 0..num_regulars {
336 for right in left + 1..num_regulars {
337 pairs.push((left, right));
338 }
339 }
340 Some(pairs)
341}
342
343#[crate::aggregate_reduction(identity)]
344impl crate::rules::AggregateReductionResult for ReductionThreeDimensionalMatchingToThreePartition {}
345
346#[reduction(
347 transform = upper_bound {
348 bound = "40960 * (32 * universe_size)^4 + 964",
350 max_numeric_magnitude_bits = "4 * universe_size + 36",
351 num_elements = "24 * num_triples * num_triples - 3 * num_triples + 6",
352 num_groups = "8 * num_triples * num_triples - num_triples + 2",
353 })]
354impl ReduceTo<ThreePartition> for ThreeDimensionalMatching {
355 type Result = ReductionThreeDimensionalMatchingToThreePartition;
356
357 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
358 let q = self.universe_size();
359 let t = self.num_triples();
360
361 if q == 0 {
362 return Ok(ReductionThreeDimensionalMatchingToThreePartition {
364 target: ThreePartition::new(vec![1, 1, 1], 3),
365 step2_items: Vec::new(),
366 pair_keys: Vec::new(),
367 num_source_triples: 0,
368 });
369 }
370
371 let mut covered_w = vec![false; q];
372 let mut covered_x = vec![false; q];
373 let mut covered_y = vec![false; q];
374 for &(w, x, y) in self.triples() {
375 covered_w[w] = true;
376 covered_x[x] = true;
377 covered_y[y] = true;
378 }
379 if covered_w.iter().any(|&covered| !covered)
380 || covered_x.iter().any(|&covered| !covered)
381 || covered_y.iter().any(|&covered| !covered)
382 {
383 return Ok(ReductionThreeDimensionalMatchingToThreePartition {
384 target: ThreePartition::new(vec![6, 6, 6, 6, 7, 9], 20),
385 step2_items: Vec::new(),
386 pair_keys: Vec::new(),
387 num_source_triples: t,
388 });
389 }
390
391 let arithmetic_overflow = |context| {
392 crate::rules::ReductionError::integer_overflow::<ThreeDimensionalMatching, ThreePartition>(
393 context,
394 )
395 };
396 let q = i64::try_from(q).map_err(|_| arithmetic_overflow("converting q to i64"))?;
397 let r = 32_i64
398 .checked_mul(q)
399 .ok_or_else(|| arithmetic_overflow("computing r = 32q"))?;
400 let r2 = r
401 .checked_mul(r)
402 .ok_or_else(|| arithmetic_overflow("computing r^2"))?;
403 let r3 = r2
404 .checked_mul(r)
405 .ok_or_else(|| arithmetic_overflow("computing r^3"))?;
406 let r4 = r3
407 .checked_mul(r)
408 .ok_or_else(|| arithmetic_overflow("computing r^4"))?;
409 let target1 = 40_i64
410 .checked_mul(r4)
411 .ok_or_else(|| arithmetic_overflow("computing the ABCD-Partition target"))?;
412
413 let mut step2_items = Vec::with_capacity(4 * t);
414 let mut step2_values = Vec::with_capacity(4 * t);
415
416 let mut seen_w = std::collections::HashSet::new();
417 let mut seen_x = std::collections::HashSet::new();
418 let mut seen_y = std::collections::HashSet::new();
419
420 for (source_triple, &(w, x, y)) in self.triples().iter().enumerate() {
421 let w_num = i64::try_from(w).map_err(|_| arithmetic_overflow("converting w to i64"))?;
422 let x_num = i64::try_from(x).map_err(|_| arithmetic_overflow("converting x to i64"))?;
423 let y_num = i64::try_from(y).map_err(|_| arithmetic_overflow("converting y to i64"))?;
424
425 let a_value = 10_i64
426 .checked_mul(r4)
427 .and_then(|value| value.checked_sub(y_num.checked_mul(r3)?))
428 .and_then(|value| value.checked_sub(x_num.checked_mul(r2)?))
429 .and_then(|value| value.checked_sub(w_num.checked_mul(r)?))
430 .ok_or_else(|| arithmetic_overflow("computing an A item"))?;
431 let step2_a = 16_i64
432 .checked_mul(a_value)
433 .and_then(|value| value.checked_add(1))
434 .ok_or_else(|| arithmetic_overflow("encoding an A item"))?;
435 step2_values.push(step2_a);
436 step2_items.push(Step2Item::A {
437 source_triple,
438 w,
439 x,
440 y,
441 });
442
443 let w_first = seen_w.insert(w);
444 let b_digit: i64 = if w_first { 10 } else { 11 };
445 let b_value = b_digit
446 .checked_mul(r4)
447 .and_then(|value| value.checked_add(w_num.checked_mul(r)?))
448 .ok_or_else(|| arithmetic_overflow("computing a B item"))?;
449 let step2_b = 16_i64
450 .checked_mul(b_value)
451 .and_then(|value| value.checked_add(2))
452 .ok_or_else(|| arithmetic_overflow("encoding a B item"))?;
453 step2_values.push(step2_b);
454 step2_items.push(Step2Item::B {
455 w,
456 first_occurrence: w_first,
457 });
458
459 let x_first = seen_x.insert(x);
460 let c_digit: i64 = if x_first { 10 } else { 11 };
461 let c_value = c_digit
462 .checked_mul(r4)
463 .and_then(|value| value.checked_add(x_num.checked_mul(r2)?))
464 .ok_or_else(|| arithmetic_overflow("computing a C item"))?;
465 let step2_c = 16_i64
466 .checked_mul(c_value)
467 .and_then(|value| value.checked_add(4))
468 .ok_or_else(|| arithmetic_overflow("encoding a C item"))?;
469 step2_values.push(step2_c);
470 step2_items.push(Step2Item::C {
471 x,
472 first_occurrence: x_first,
473 });
474
475 let y_first = seen_y.insert(y);
476 let d_digit: i64 = if y_first { 10 } else { 8 };
477 let d_value = d_digit
478 .checked_mul(r4)
479 .and_then(|value| value.checked_add(y_num.checked_mul(r3)?))
480 .ok_or_else(|| arithmetic_overflow("computing a D item"))?;
481 let step2_d = 16_i64
482 .checked_mul(d_value)
483 .and_then(|value| value.checked_add(8))
484 .ok_or_else(|| arithmetic_overflow("encoding a D item"))?;
485 step2_values.push(step2_d);
486 step2_items.push(Step2Item::D {
487 y,
488 first_occurrence: y_first,
489 });
490 }
491
492 let target2 = 16_i64
493 .checked_mul(target1)
494 .and_then(|value| value.checked_add(15))
495 .ok_or_else(|| arithmetic_overflow("computing the 4-Partition target"))?;
496 let pair_keys = enumerate_pair_keys(step2_values.len())
497 .ok_or_else(|| arithmetic_overflow("computing the 4-Partition pair count"))?;
498
499 let subtract_fillers =
500 3usize.checked_mul(t).ok_or_else(|| {
501 crate::rules::ReductionError::integer_overflow::<
502 ThreeDimensionalMatching,
503 ThreePartition,
504 >("computing the filler count")
505 })?;
506 let num_fillers = 8usize
507 .checked_mul(t)
508 .and_then(|value| value.checked_mul(t))
509 .and_then(|value| value.checked_sub(subtract_fillers))
510 .ok_or_else(|| {
511 crate::rules::ReductionError::integer_overflow::<
512 ThreeDimensionalMatching,
513 ThreePartition,
514 >("computing the filler count")
515 })?;
516
517 let pair_elements =
518 2usize.checked_mul(pair_keys.len()).ok_or_else(|| {
519 crate::rules::ReductionError::integer_overflow::<
520 ThreeDimensionalMatching,
521 ThreePartition,
522 >("computing the number of pair elements")
523 })?;
524 let total_elements = step2_values
525 .len()
526 .checked_add(pair_elements)
527 .and_then(|value| value.checked_add(num_fillers))
528 .ok_or_else(|| {
529 crate::rules::ReductionError::integer_overflow::<
530 ThreeDimensionalMatching,
531 ThreePartition,
532 >("computing the target element count")
533 })?;
534
535 let mut sizes = Vec::with_capacity(total_elements);
536
537 for &step2_value in &step2_values {
538 let regular = 5_i64
539 .checked_mul(target2)
540 .and_then(|value| value.checked_add(step2_value))
541 .and_then(|value| value.checked_mul(4))
542 .and_then(|value| value.checked_add(1))
543 .ok_or_else(|| arithmetic_overflow("computing a regular element"))?;
544 sizes.push(regular);
545 }
546
547 for &(left, right) in &pair_keys {
548 let pair_sum = step2_values[left]
549 .checked_add(step2_values[right])
550 .ok_or_else(|| arithmetic_overflow("summing paired 4-Partition elements"))?;
551 let u_value = 6_i64
552 .checked_mul(target2)
553 .and_then(|value| value.checked_sub(pair_sum))
554 .and_then(|value| value.checked_mul(4))
555 .and_then(|value| value.checked_add(2))
556 .ok_or_else(|| arithmetic_overflow("computing a pairing u element"))?;
557 sizes.push(u_value);
558
559 let uprime_value = 5_i64
560 .checked_mul(target2)
561 .and_then(|value| value.checked_add(pair_sum))
562 .and_then(|value| value.checked_mul(4))
563 .and_then(|value| value.checked_add(2))
564 .ok_or_else(|| arithmetic_overflow("computing a pairing u' element"))?;
565 sizes.push(uprime_value);
566 }
567
568 let filler_value = 20_i64
569 .checked_mul(target2)
570 .ok_or_else(|| arithmetic_overflow("computing a filler element"))?;
571 sizes.extend(std::iter::repeat_n(filler_value, num_fillers));
572
573 let bound = 64_i64
574 .checked_mul(target2)
575 .and_then(|value| value.checked_add(4))
576 .ok_or_else(|| arithmetic_overflow("computing the 3-Partition bound"))?;
577
578 Ok(ReductionThreeDimensionalMatchingToThreePartition {
579 target: ThreePartition::try_new(sizes, bound)
580 .map_err(<Self as ReduceTo<ThreePartition>>::target_construction)?,
581 step2_items,
582 pair_keys,
583 num_source_triples: t,
584 })
585 }
586}
587
588#[cfg(feature = "example-db")]
589pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
590 use crate::export::SolutionPair;
591
592 vec![crate::example_db::specs::RuleExampleSpec {
593 id: "threedimensionalmatching_to_threepartition",
594 build: || {
595 crate::example_db::specs::rule_example_with_witness::<_, ThreePartition>(
596 ThreeDimensionalMatching::new(1, vec![(0, 0, 0)]),
597 SolutionPair {
598 source_config: serde_json::json!(vec![true]),
599 target_config: serde_json::json!(vec![
600 0, 0, 1, 1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 2, 3, 4, 5, 6,
601 ]),
602 },
603 )
604 },
605 }]
606}
607
608#[cfg(test)]
609#[path = "../unit_tests/rules/threedimensionalmatching_threepartition.rs"]
610mod tests;