Skip to content
Closed
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
6 changes: 3 additions & 3 deletions docs/paper/reductions.typ
Original file line number Diff line number Diff line change
Expand Up @@ -11900,7 +11900,7 @@ the displayed rule, extracted from the corresponding `pred path` entry.
)[
This reduction encodes vertex cover as a minimum-weight solution subgraph problem on a three-layer AND/OR DAG. The root AND gate requires all edges to be covered; each edge becomes an OR gate selecting which endpoint covers it; and each vertex becomes a sink whose arc weight equals the vertex weight. The minimum-weight solution subgraph selects exactly the arcs corresponding to a minimum vertex cover.
][
_Construction._ Given a Minimum Vertex Cover instance $(G = (V, E), bold(w))$ with $n = |V|$ vertices and $m = |E|$ edges, build an AND/OR graph $D$ with $1 + m + 2n$ vertices arranged in three layers:
_Construction._ Given a Minimum Vertex Cover instance $(G = (V, E), bold(w))$, let $N = {v in V : w_v < 0}$. Every optimum contains $N$: adding an omitted negative-weight vertex preserves coverage and strictly decreases cost. Remove all edges incident to $N$ and set the residual weights of vertices in $N$ to zero. Keep the original vertex indices. Below, $E$ denotes these residual edges, $m = |E|$, and $w$ denotes the nonnegative residual weights; the original optimum equals the residual optimum plus $sum_(v in N) w_v$ using the original weights. Build an AND/OR graph $D$ with $1 + m + 2n$ vertices, where $n = |V|$:

- *Root (AND gate):* A single vertex $r$ (index 0) with gate type AND.
- *Edge layer (OR gates):* For each edge $e_i = {u, v}$ ($i = 0, dots, m-1$), create vertex $e_i$ (index $1 + i$) with gate type OR and an arc $(r, e_i)$ of weight 1.
Expand All @@ -11909,9 +11909,9 @@ the displayed rule, extracted from the corresponding `pred path` entry.

Since $r$ is AND, any solution subgraph must include all arcs from $r$ to the edge-layer vertices (cost $m$). Each edge-OR vertex $e_i$ requires at least one of its two outgoing arcs to $c_u$ and $c_v$ (selecting which endpoint covers edge $i$). Each activated cover vertex $c_j$ requires its outgoing arc to $s_j$ (contributing $w_j$). The total weight is $m + |{"activated cover arcs"}| + sum_(j in C) w_j$.

_Correctness._ ($arrow.r.double$) If $C subset.eq V$ is a vertex cover with weight $W$, then for each edge $e_i = {u, v}$, at least one endpoint lies in $C$; select the arc from $e_i$ to that endpoint's cover vertex. Activate all cover-to-sink arcs for vertices in $C$. This satisfies the AND gate at the root (all edge arcs selected), every edge OR gate (at least one child selected), and all activated cover vertices (sink arc selected). The total weight is $m + |{"edge-to-cover arcs"}| + W$. ($arrow.l.double$) In any valid solution subgraph, the AND root forces all $m$ edge arcs. Each edge OR vertex selects at least one arc to a cover vertex, activating that cover vertex and its sink arc. The set of activated cover vertices forms a vertex cover (every edge has at least one endpoint activated). The sink arc weights sum to the cover weight, so any minimum-weight solution subgraph corresponds to a minimum vertex cover.
_Correctness._ ($arrow.r.double$) Given a residual cover $C$, choose exactly one endpoint in $C$ for each edge. Activate only cover vertices reached by these choices, and their sink arcs. This is a valid solution of cost at most $2m + w(C)$, since omitted cover vertices have nonnegative weight. ($arrow.l.double$) Any valid target solution activates a residual cover $C$ and costs at least $2m + w(C)$: the root requires all $m$ arcs and each edge gate requires at least one unit-weight outgoing arc. These bounds imply that the target optimum is exactly $2m$ plus the residual cover optimum, and every target optimum recovers an optimal residual cover. Adding $N$ restores an original optimum, including negative vertices that are isolated or redundant for coverage.

_Solution extraction._ Examine the cover-to-sink arcs (indices $3m, dots, 3m + n - 1$ in the arc list): $c_j = 1$ if arc $(c_j, s_j)$ is selected, $c_j = 0$ otherwise.
_Solution extraction._ Select every vertex in $N$, together with vertices whose cover-to-sink arcs (indices $3m, dots, 3m + n - 1$) are selected. Evaluate this cover using the original weights.
]

#reduction-rule("MaximumMatching", "MaximumSetPacking")[
Expand Down
23 changes: 16 additions & 7 deletions src/rules/minimumvertexcover_minimumweightandorgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::topology::SimpleGraph;
pub struct ReductionVCToAndOrGraph {
target: MinimumWeightAndOrGraph,
sink_arc_start: usize,
num_source_vertices: usize,
forced_cover: Vec<bool>,
}

impl ReductionResult for ReductionVCToAndOrGraph {
Expand All @@ -30,15 +30,17 @@ impl ReductionResult for ReductionVCToAndOrGraph {
crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;

Ok({
(0..self.num_source_vertices)
.map(|j| target_solution[self.sink_arc_start + j])
self.forced_cover
.iter()
.enumerate()
.map(|(j, &forced)| forced || target_solution[self.sink_arc_start + j])
.collect()
})
}
}

#[reduction(
transform = exact {
transform = upper_bound {
num_vertices = "1 + num_edges + 2 * num_vertices",
num_arcs = "3 * num_edges + num_vertices",
}
Expand All @@ -48,7 +50,14 @@ impl ReduceTo<MinimumWeightAndOrGraph> for MinimumVertexCover<SimpleGraph, i64>

fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
let n = self.graph().num_vertices();
let edges = self.graph().edges();
// Every optimum includes negative-weight vertices, even isolated ones.
let forced_cover: Vec<_> = self.weights().iter().map(|&w| w < 0).collect();
let edges: Vec<_> = self
.graph()
.edges()
.into_iter()
.filter(|&(u, v)| !forced_cover[u] && !forced_cover[v])
.collect();
let m = edges.len();

let num_target_vertices = 1 + m + (2 * n);
Expand Down Expand Up @@ -80,7 +89,7 @@ impl ReduceTo<MinimumWeightAndOrGraph> for MinimumVertexCover<SimpleGraph, i64>
let sink_arc_start = arcs.len();
for (j, &weight) in self.weights().iter().enumerate() {
arcs.push((cover_vertex(j), sink_vertex(j)));
arc_weights.push(weight);
arc_weights.push(weight.max(0));
}

let target =
Expand All @@ -89,7 +98,7 @@ impl ReduceTo<MinimumWeightAndOrGraph> for MinimumVertexCover<SimpleGraph, i64>
Ok(ReductionVCToAndOrGraph {
target,
sink_arc_start,
num_source_vertices: n,
forced_cover,
})
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/unit_tests/rules/minimumvertexcover_minimumweightandorgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ fn test_minimumvertexcover_to_minimumweightandorgraph_closed_loop() {
);
}

#[test]
fn negative_vertices_are_selected_even_when_isolated_or_redundant() {
for (n, edges, weights) in [
(1, vec![], vec![-1]),
(2, vec![(0, 1)], vec![-1, -1]),
(3, vec![(0, 1), (1, 2)], vec![-2, 3, 1]),
(2, vec![(0, 0), (0, 1)], vec![-1, 2]),
] {
let source = MinimumVertexCover::new(SimpleGraph::new(n, edges), weights);
let reduction = ReduceTo::<MinimumWeightAndOrGraph>::reduce_to(&source).unwrap();
assert_optimization_round_trip_from_optimization_target(
&source,
&reduction,
"signed vertex cover",
);
}
}

#[test]
fn test_reduction_structure() {
let source = issue_example_source();
Expand Down
Loading