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