Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -19847,6 +19847,59 @@ The following table shows concrete target-variable counts for example instances,
_Solution extraction._ Return the ILP solution vector directly: $x_l = 1$ iff triple $m_l$ is selected.
]

#let tdm_x3c = load-example("ThreeDimensionalMatching", "ExactCoverBy3Sets")
#let tdm_x3c_sol = tdm_x3c.solutions.at(0)
#reduction-rule("ThreeDimensionalMatching", "ExactCoverBy3Sets",
example: true,
example-caption: [$q = #tdm_x3c.source.instance.universe_size$, $t = #tdm_x3c.source.instance.triples.len()$ triples $arrow.r$ #tdm_x3c.target.instance.subsets.len() subsets over $3q = #tdm_x3c.target.instance.universe_size$ elements],
extra: [
#pred-commands(
"pred create --example " + rule-spec(tdm_x3c) + " -o three-dimensional-matching.json",
"pred reduce three-dimensional-matching.json --via route.json -o bundle.json",
"pred solve bundle.json",
"pred evaluate three-dimensional-matching.json --config " + cli-config(tdm_x3c_sol.source_config),
)

#{
let q = tdm_x3c.source.instance.universe_size
let triples = tdm_x3c.source.instance.triples
let subsets = tdm_x3c.target.instance.subsets
let selected = tdm_x3c_sol.source_config.enumerate().filter(((i, x)) => x).map(((i, x)) => i)
// For each selected subset, an element that no other subset contains.
let private = selected.map(i => (i, subsets.at(i).find(e => subsets.filter(s => s.contains(e)).len() == 1)))
assert(private.all(((i, e)) => e != none), message: "the uniqueness argument needs a private element in every selected subset")
let domains = ($w$, $x$, $y$)
[
*Step 1 -- Source instance.* The canonical source has $q = #q$ and $t = #triples.len()$ triples: #triples.map(tr => "(" + fmt-values(tr) + ")").join(", ").

*Step 2 -- Tag the coordinates.* The target universe is ${0, dots, #(3 * q - 1)}$: $W$ occupies $0, dots, #(q - 1)$, $X$ occupies $#q, dots, #(2 * q - 1)$ and $Y$ occupies $#(2 * q), dots, #(3 * q - 1)$. Triple $(w, x, y)$ becomes ${w, #q + x, #(2 * q) + y}$, giving the subsets #subsets.enumerate().map(((i, s)) => $S_#i = {#fmt-values(s)}$).join(", ").

*Step 3 -- Verify the witness.* The configuration $(#fmt-values(tdm_x3c_sol.target_config))$ selects #selected.map(i => $S_#i$).join(", "), which are pairwise disjoint and cover all $#(3 * q)$ elements #sym.checkmark. The identical source configuration selects the triples #selected.map(i => "(" + fmt-values(triples.at(i)) + ")").join(", "), a perfect matching #sym.checkmark.

*Multiplicity:* The fixture stores one canonical witness, and it is the only exact cover: #private.map(((i, e)) => [element $#e$ ($#domains.at(calc.quo(e, q)) = #calc.rem(e, q)$) lies only in $S_#i$]).join(", "), so every exact cover contains #selected.map(i => $S_#i$).join(", "), which already cover $U$.
]
}
],
)[
Karp's 3DM and X3C @karp1972 @garey1979 are related by tagging: the three coordinate domains $W = X = Y = {0, dots, q - 1}$ become the disjoint blocks of a $3q$-element universe, and each triple becomes one 3-element subset. The target has $3q$ elements and $t$ subsets, and the selection vector is unchanged.
][
_Construction._ Let the source have universe size $q$ and triples $m_l = (a_l, b_l, c_l)$ for $l = 0, dots, t - 1$, with $a_l, b_l, c_l in {0, dots, q - 1}$. Set $U = {0, dots, 3q - 1}$ and $S_l = {a_l, q + b_l, 2q + c_l}$. Since $a_l < q <= q + b_l < 2q <= 2q + c_l < 3q$, each $S_l$ has three distinct elements of $U$, even when $a_l$, $b_l$, $c_l$ are numerically equal, and $|U|$ is divisible by 3; so $(U, (S_0, dots, S_(t-1)))$ is a valid X3C instance. Duplicate triples give duplicate subsets at their own positions.

The map $tau(d, v) = d q + v$ from tagged coordinates (domain $d in {0, 1, 2}$, value $v < q$) to $U$ is a bijection, and $S_l = tau({(0, a_l), (1, b_l), (2, c_l)})$.

_Correctness._ Fix a selection $J subset.eq {0, dots, t - 1}$.

($arrow.r.double$) Suppose ${m_l : l in J}$ is a perfect matching: $|J| = q$ and no two selected triples share a coordinate in any domain. If two selected subsets $S_l, S_k$ shared an element $u$, then $u = tau(d, v)$ for a unique $(d, v)$, so both triples would have value $v$ in domain $d$, a contradiction; the selected subsets are pairwise disjoint. Their union then has $3q = |U|$ elements, so it is $U$, and $J$ is an exact cover.

($arrow.l.double$) Suppose ${S_l : l in J}$ is an exact cover: $|J| = q$ and every element of $U$ lies in exactly one selected subset. If two selected triples agreed in domain $d$ with value $v$, both subsets would contain $tau(d, v)$, contradicting disjointness. So $|J| = q$ and no coordinate repeats: $J$ selects a perfect matching.

Both problems also reject a selection whose length differs from $t$, so the two instances have exactly the same solution vectors.

_Solution extraction._ Return the target selection vector unchanged after checking that it is an exact cover.

_NO example._ Take $q = 3$ and the five triples $(0, 0, 0)$, $(1, 1, 1)$, $(2, 1, 2)$, $(2, 0, 2)$, $(1, 0, 1)$. The construction gives $U = {0, dots, 8}$ and the subsets ${0, 3, 6}$, ${1, 4, 7}$, ${2, 4, 8}$, ${2, 3, 8}$, ${1, 3, 7}$. On the source side, the $X$-value $2$ occurs in no triple; any $3$ selected triples with pairwise distinct $X$-values would have to use all of $0, 1, 2$, so no selection is a perfect matching. On the target side, the tagged element $q + 2 = 5$ lies in no subset, so no selection covers $U$ and no exact cover exists. Both instances therefore have no solution, as the correctness argument requires.
]

#let tdm_mwd = load-example("ThreeDimensionalMatching", "MinimumWeightDecoding")
#let tdm_mwd_sol = tdm_mwd.solutions.at(0)
#reduction-rule("ThreeDimensionalMatching", "MinimumWeightDecoding",
Expand Down
3 changes: 2 additions & 1 deletion src/models/set/exact_cover_by_3_sets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ impl ExactCoverBy3Sets {
Self::try_new(universe_size, subsets).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
/// Fallible constructor shared by [`Self::new`], serde, and reductions.
pub(crate) fn try_new(
universe_size: usize,
mut subsets: Vec<[usize; 3]>,
) -> Result<Self, crate::registry::ConstructionError> {
Expand Down
2 changes: 2 additions & 0 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ pub(crate) mod test_helpers;
#[cfg(test)]
#[path = "../unit_tests/rules/aggregate_contracts.rs"]
mod aggregate_contracts;
pub(crate) mod threedimensionalmatching_exactcoverby3sets;
pub(crate) mod threedimensionalmatching_minimumweightdecoding;
pub(crate) mod threedimensionalmatching_threepartition;
pub(crate) mod threepartition_resourceconstrainedscheduling;
Expand Down Expand Up @@ -466,6 +467,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
specs.extend(maxcut_minimumcutintoboundedsets::canonical_rule_example_specs());
specs.extend(maxcut_minimummatrixcover::canonical_rule_example_specs());
specs.extend(partition_binpacking::canonical_rule_example_specs());
specs.extend(threedimensionalmatching_exactcoverby3sets::canonical_rule_example_specs());
specs.extend(threedimensionalmatching_minimumweightdecoding::canonical_rule_example_specs());
specs.extend(threedimensionalmatching_threepartition::canonical_rule_example_specs());
specs.extend(threepartition_resourceconstrainedscheduling::canonical_rule_example_specs());
Expand Down
100 changes: 100 additions & 0 deletions src/rules/threedimensionalmatching_exactcoverby3sets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Reduction from ThreeDimensionalMatching to ExactCoverBy3Sets.
//!
//! The three coordinate domains `W = X = Y = {0, ..., q-1}` become the tagged
//! disjoint union `{0, ..., 3q-1}`: `w` maps to `w`, `x` to `q + x`, and `y` to
//! `2q + y`. Triple `(w, x, y)` becomes the subset `{w, q + x, 2q + y}`, and the
//! selection vector is shared, so solution extraction is the identity.

use crate::models::set::{ExactCoverBy3Sets, ThreeDimensionalMatching};
use crate::reduction;
use crate::rules::traits::{ReduceTo, ReductionResult};

/// Result of reducing ThreeDimensionalMatching to ExactCoverBy3Sets.
#[derive(Debug, Clone)]
pub struct ReductionThreeDimensionalMatchingToExactCoverBy3Sets {
target: ExactCoverBy3Sets,
}

impl ReductionResult for ReductionThreeDimensionalMatchingToExactCoverBy3Sets {
type Source = ThreeDimensionalMatching;
type Target = ExactCoverBy3Sets;

fn target_problem(&self) -> &Self::Target {
&self.target
}

/// Target subset `i` is the tagged image of source triple `i`, so an exact
/// cover selects exactly the triples of a perfect matching.
fn extract_solution(
&self,
target_solution: &<Self::Target as crate::traits::Problem>::Solution,
) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
crate::rules::traits::validate_target_witness(
self.target_problem(),
target_solution,
|value| value.0,
"target witness is not an exact cover",
)?;

Ok(target_solution.clone())
}
}

#[crate::aggregate_reduction(identity)]
impl crate::rules::AggregateReductionResult
for ReductionThreeDimensionalMatchingToExactCoverBy3Sets
{
}

#[reduction(transform = exact {
universe_size = "3 * universe_size",
num_subsets = "num_triples",
})]
impl ReduceTo<ExactCoverBy3Sets> for ThreeDimensionalMatching {
type Result = ReductionThreeDimensionalMatchingToExactCoverBy3Sets;

fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
let q = self.universe_size();
let target_universe_size = q.checked_mul(3).ok_or_else(|| {
crate::rules::ReductionError::integer_overflow::<Self, ExactCoverBy3Sets>(
"computing the tagged universe size 3 * universe_size",
)
})?;
// Every coordinate is below q, so each tagged element is below 3q and
// the additions cannot overflow once 3q fits in usize.
let subsets = self
.triples()
.iter()
.map(|&(w, x, y)| [w, q + x, 2 * q + y])
.collect();
let target = ExactCoverBy3Sets::try_new(target_universe_size, subsets)
.map_err(<Self as ReduceTo<ExactCoverBy3Sets>>::target_construction)?;

Ok(ReductionThreeDimensionalMatchingToExactCoverBy3Sets { target })
}
}

#[cfg(feature = "example-db")]
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
use crate::export::SolutionPair;

vec![crate::example_db::specs::RuleExampleSpec {
id: "threedimensionalmatching_to_exactcoverby3sets",
build: || {
crate::example_db::specs::rule_example_with_witness::<_, ExactCoverBy3Sets>(
ThreeDimensionalMatching::new(
3,
vec![(0, 0, 0), (1, 1, 1), (2, 2, 2), (0, 1, 2), (1, 2, 0)],
),
SolutionPair {
source_config: serde_json::json!(vec![true, true, true, false, false]),
target_config: serde_json::json!(vec![true, true, true, false, false]),
},
)
},
}]
}

#[cfg(test)]
#[path = "../unit_tests/rules/threedimensionalmatching_exactcoverby3sets.rs"]
mod tests;
Loading
Loading