Skip to main content

problemreductions/rules/
threedimensionalmatching_threepartition.rs

1//! Reduction from ThreeDimensionalMatching to ThreePartition.
2//!
3//! This follows the classical three-step chain:
4//! 1. 3DM -> ABCD-Partition
5//! 2. ABCD-Partition -> 4-Partition
6//! 3. 4-Partition -> 3-Partition
7
8use 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/// Result of reducing ThreeDimensionalMatching to ThreePartition.
49#[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    /// Reverse the 4-Partition -> 3-Partition pairing gadget, then decode the
296    /// surviving real ABCD groups back into selected source triples.
297    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;