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
37 changes: 37 additions & 0 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -14739,6 +14739,43 @@ The following reductions to Integer Linear Programming are straightforward formu
_Solution extraction._ The hitting set $H$ is directly the vertex cover: $c_v = h_v$ for each $v in V$.
]

// MinimumDominatingSet → MinimumHittingSet (#1096)
#let mds_hs = load-example("MinimumDominatingSet", "MinimumHittingSet")
#let mds_hs_sol = mds_hs.solutions.at(0)
#let mds_hs_n = graph-num-vertices(mds_hs.source.instance)
#let mds_hs_edges = graph-instance(mds_hs.source.instance).graph.edges.map(e => (e.at(0), e.at(1)))
#let mds_hs_rows = mds_hs.target.instance.sets
#let mds_hs_closed(v) = range(mds_hs_n).filter(u => u == v or mds_hs_edges.any(e => (e.at(0) == u and e.at(1) == v) or (e.at(0) == v and e.at(1) == u)))
#let mds_hs_pick = mds_hs_sol.source_config.enumerate().filter(((i, x)) => x).map(((i, x)) => i)
#let mds_hs_hits(pick, row) = row.any(u => pick.contains(u))
#assert(mds_hs_rows == range(mds_hs_n).map(mds_hs_closed), message: "target rows must be the closed neighborhoods")
#assert(mds_hs_rows.map(r => r.len()).sum() == mds_hs_n + 2 * mds_hs_edges.len(), message: "incidence count n + 2m")
#assert(mds_hs_rows.all(r => mds_hs_hits(mds_hs_pick, r)), message: "stored witness must hit every row")
#assert(range(mds_hs_n).all(v => not mds_hs_rows.all(r => mds_hs_hits((v,), r))), message: "no singleton may hit every row")
#assert(mds_hs_pick.len() == 2, message: "optimum is 2")
#reduction-rule("MinimumDominatingSet", "MinimumHittingSet",
example: true,
example-caption: [Unit-weight Dominating Set to Hitting Set (path, $n = #mds_hs_n$, $|E| = #mds_hs_edges.len()$)],
extra: [
#pred-commands(
"pred create --example " + rule-spec(mds_hs) + " -o mds.json",
"pred reduce mds.json --via route.json -o bundle.json",
"pred solve bundle.json",
"pred evaluate mds.json --config " + cli-config(mds_hs_sol.source_config),
)
*YES example.* The path has edges #mds_hs_edges.map(e => $(#e.at(0), #e.at(1))$).join(", "), so the rows are #mds_hs_rows.map(r => $\{#fmt-values(r)\}$).join(", ") with #mds_hs_rows.map(r => r.len()).sum() $= #mds_hs_n + 2 dot #mds_hs_edges.len()$ incidences in total. The selection $H = {#fmt-values(mds_hs_pick)}$ meets every row, hence dominates the path, and has size #mds_hs_pick.len(). \
*NO example (bound $k = 1$).* The empty selection hits no row. Each of the #mds_hs_n singletons misses a row: #range(mds_hs_n).map(v => { let miss = mds_hs_rows.position(r => not r.contains(v)); [$#v$ misses $F_#miss = \{#fmt-values(mds_hs_rows.at(miss))\}$] }).join(", "). So neither the Dominating Set nor the Hitting Set question with bound 1 is satisfiable, and the optimum of both is #mds_hs_pick.len().
],
)[
A set of vertices dominates a graph exactly when it hits the closed neighborhood of every vertex; this observation is stated in the literature on Hitting Set kernelization @bannach2018. It is restricted to unit weights because Hitting Set has no element weights (definitions in @garey1979).
][
_Construction._ Given a simple graph $G = (V, E)$ with $V = {0, dots, n - 1}$ and unit weights, let $N[v] = {v} union {u : {u, v} in E}$. Output the Hitting Set instance with universe $U = V$ and one subset $F_v = N[v]$ for each $v in V$, in vertex order and keeping equal subsets. Then $|U| = n$, $|cal(F)| = n$, and the subsets contain $n + 2 |E|$ elements in total.

_Correctness._ ($arrow.r.double$) If $S$ dominates $G$ and $v in V$, then either $v in S$ or some $u in S$ is adjacent to $v$; in both cases $S inter N[v] != emptyset$, so $S$ hits every $F_v$. ($arrow.l.double$) If $H$ hits every $F_v$, pick $h in H inter N[v]$ for a vertex $v$. Then $h = v$ or $h$ is adjacent to $v$, so $v$ is dominated by $H$. As the same vertex set is used in both directions, the cardinality is preserved and the optima coincide.

_Solution extraction._ After checking that the target configuration is a hitting set, return it unchanged: universe element $v$ is vertex $v$, so every feasible hitting set, optimal or not, is a dominating set of the same size. The source model admits only simple graphs, so loops and parallel edges do not occur.
]

#reduction-rule("MinimumHittingSet", "ILP")[
Each set must contain at least one selected element -- a standard set-covering constraint on the element indicators.
][
Expand Down
11 changes: 11 additions & 0 deletions docs/paper/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -2352,3 +2352,14 @@ @article{een2006
pages = {1--26},
doi = {10.3233/SAT190014},
}

@inproceedings{bannach2018,
author = {Max Bannach and Till Tantau},
title = {Computing Hitting Set Kernels By {AC}$^{0}$-Circuits},
booktitle = {35th Symposium on Theoretical Aspects of Computer Science (STACS 2018)},
series = {Leibniz International Proceedings in Informatics (LIPIcs)},
volume = {96},
pages = {9:1--9:14},
year = {2018},
doi = {10.4230/LIPIcs.STACS.2018.9}
}
2 changes: 1 addition & 1 deletion src/models/set/minimum_hitting_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl MinimumHittingSet {
Self::try_new(universe_size, sets).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
pub(crate) fn try_new(
universe_size: usize,
mut sets: Vec<Vec<usize>>,
) -> Result<Self, crate::registry::ConstructionError> {
Expand Down
97 changes: 97 additions & 0 deletions src/rules/minimumdominatingset_minimumhittingset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//! Reduction from MinimumDominatingSet (unit-weight) to MinimumHittingSet.
//!
//! The universe is the vertex set and every vertex `v` contributes its closed
//! neighborhood `N[v]` as one set. A vertex subset hits every `N[v]` exactly
//! when it dominates the graph, so the two optimization problems share their
//! feasible configurations and their cardinalities (Bannach and Tantau, 2018).

use crate::models::graph::MinimumDominatingSet;
use crate::models::set::MinimumHittingSet;
use crate::reduction;
use crate::rules::traits::{ReduceTo, ReductionResult};
use crate::topology::{Graph, SimpleGraph};
use crate::types::One;

/// Result of reducing MinimumDominatingSet<SimpleGraph, One> to MinimumHittingSet.
#[derive(Debug, Clone)]
pub struct ReductionDSToHS {
target: MinimumHittingSet,
}

impl ReductionResult for ReductionDSToHS {
type Source = MinimumDominatingSet<SimpleGraph, One>;
type Target = MinimumHittingSet;

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

/// Solution extraction: universe element `v` is vertex `v`.
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.is_valid(),
"target configuration does not hit every closed neighborhood",
)?;

Ok(target_solution.to_vec())
}
}

#[reduction(
transform = exact {
universe_size = "num_vertices",
num_sets = "num_vertices",
}
)]
impl ReduceTo<MinimumHittingSet> for MinimumDominatingSet<SimpleGraph, One> {
type Result = ReductionDSToHS;

fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
let num_vertices = self.graph().num_vertices();

// One set per vertex: the vertex together with its neighbors.
let sets: Vec<Vec<usize>> = (0..num_vertices)
.map(|v| {
let mut set = self.neighbors(v);
set.push(v);
set
})
.collect();

let target = MinimumHittingSet::try_new(num_vertices, sets)
.map_err(<Self as ReduceTo<MinimumHittingSet>>::target_construction)?;

Ok(ReductionDSToHS { 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: "minimumdominatingset_to_minimumhittingset",
build: || {
let source = MinimumDominatingSet::new(
SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)]),
vec![One; 5],
);
crate::example_db::specs::rule_example_with_witness::<_, MinimumHittingSet>(
source,
SolutionPair {
source_config: serde_json::json!(vec![false, true, false, true, false]),
target_config: serde_json::json!(vec![false, true, false, true, false]),
},
)
},
}]
}

#[cfg(test)]
#[path = "../unit_tests/rules/minimumdominatingset_minimumhittingset.rs"]
mod tests;
2 changes: 2 additions & 0 deletions src/rules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ pub(crate) mod minimumcostmaximumflow_minimumcostcirculation;
pub(crate) mod minimumcoveringbycliques_minimumintersectiongraphbasis;
pub(crate) mod minimumdiscreteplanarinversekinematics_qubo;
mod minimumdominatingset_casts;
pub(crate) mod minimumdominatingset_minimumhittingset;
mod minimumfeedbackvertexset_casts;
pub(crate) mod minimumfeedbackvertexset_minimumcodegenerationunlimitedregisters;
pub(crate) mod minimummaximalmatching_maximumachromaticnumber;
Expand Down Expand Up @@ -484,6 +485,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
specs.extend(minimumvertexcover_minimumfeedbackarcset::canonical_rule_example_specs());
specs.extend(minimumvertexcover_minimumfeedbackvertexset::canonical_rule_example_specs());
specs.extend(minimumvertexcover_minimumhittingset::canonical_rule_example_specs());
specs.extend(minimumdominatingset_minimumhittingset::canonical_rule_example_specs());
specs.extend(minimumvertexcover_minimumsetcovering::canonical_rule_example_specs());
specs.extend(minimumvertexcover_minimumweightandorgraph::canonical_rule_example_specs());
specs.extend(naesatisfiability_setsplitting::canonical_rule_example_specs());
Expand Down
Loading
Loading